data-structurescommon-lisphashtablesbclwritefile

Interference Between a Structure's :print-function and *print-readably* in Common Lisp?


I'm trying to readably print a common lisp structure to a file so it can be read back in later. It appears SBCL has some rather sophisticated built-in facilities for readably printing complex objects, which may obviate having to write specialized print-object methods.

Is it possible that my structure's own :print-function is interfering with *print-readably*?

(defstruct (problem-state (:conc-name problem-state.) (:print-function print-problem-state) (:copier nil))
  "A planning state including the current propositional database."
  (name nil :type symbol)  ;last action executed
  (instantiations nil :type list)  ;from last action effect
  (happenings nil :type list)  ;a list of (object (next-index next-time next-direction)) pairs
  (time 0.0 :type real)
  (value 0.0 :type real)
  (heuristic 0.0 :type real)
  (idb (make-hash-table) :type hash-table)  ;integer hash table of propositions
  (hidb (make-hash-table) :type hash-table))  ;integer table for happening events

When executing

(defun save (object file)
  "Saves an object to a file so it can be read in later."
  (with-open-file (out-stream file :direction :output)
    (let ((*print-readably* t))
      (pprint object out-stream))))

for a structure instance, the result is human readable, but not lisp readable.

Do I need to somehow disable the :print-function (if this is the problem)?


Solution

  • If you provide a :print-function or :print-object option to defstruct, then the printer you designate will be used to print objects of that class. It's the responsibility of that function to deal with the printer control variables correctly. In particular this means that if *print-readably* is true it must either print the object readably or signal an error if it's not willing or unable to do so: it should not simply print the thing unreadably, and still less should it bind or assign to *print-readably* or anything like that.

    print-unreadable-object can help you get this behaviour correct:

    (defstruct (foo
                (:print-function (lambda (f s d)
                                   (declare (ignore d))
                                   (print-unreadable-object (f s :type t :identity t)
                                     ...))))
      ...)
    

    will behave correctly, as print-unreadable-object is defined to signal a suitable error if *print-readably* is true.

    If you want to be able to print a structure readably if need be but unreadably otherwise it is relatively hard to do this using the :print-function or :print-object mechanisms. Your function clearly doesn't get this correct: I suspect few do.

    Fortunately there is an easy answer, which is not to use these options, but define methods on print-object:

    (defstruct foo
      x)
    
    (defmethod print-object ((f foo) s)
      (if *print-readably*
          (call-next-method)
        (print-unreadable-object (f s :type t :identity t)
          (format s "x = ~A" (foo-x f)))))
    

    And now

    > *print-readably*
    nil
    
    > (let ((*print-readably* t))
        (print (make-foo)))
    
    #S(foo :x nil) 
    #<foo x = nil 8010058923>
    

    Of course all the :print-function and :print-object mechanisms do is define stylized methods on print-object for you. From defstruct:

    The :print-function and :print-object options specify that a print-object method for structures of type structure-name should be generated.

    Before CLOS (when :print-object didn't exist) :print-function did something in a more deeply magic way, but that's all been greatly simplified by CLOS.