From: Thomas Schulte
Subject: Help using make-load-form in LispWorks CL
Date: 
Message-ID: <a18ee94d.0307121505.11fcde1f@posting.google.com>
I hope I'm not running over well-traveled ground for this group, but I
didn't see the answer to this question in previous posts.  I am trying
to use make-load-form to save and later retrieve an object.

A simple class:

(defclass test ()
  ((slot-a :initarg :a
           :accessor a)))

(defmethod make-load-form ((self test) &optional environment)
    (make-load-form-saving-slots self))

And how I am trying to use it:

CL-USER 6 : 1 > (setq obj1 (make-instance 'test :a 1)) 
#<TEST 20622AF4>

CL-USER 7 : 1 > (make-load-form obj1)
(CLOS::MLF-ALLOCATE-INSTANCE (QUOTE TEST))
(CLOS::MLF-SET-INSTANCE-SLOTS #<TEST 20622AF4> (QUOTE ((SLOT-A . 1))))

CL-USER 8 : 1 > (with-open-file (file-out
"c:\\revcalc\\lisp\\test-out.lisp" :direction :output :if-exists
:supersede)
                  (multiple-value-bind (a b) (make-load-form obj1)
                    (prin1 a file-out)
                    (prin1 b file-out)))
(CLOS::MLF-SET-INSTANCE-SLOTS #<TEST 20622AF4> (QUOTE ((SLOT-A . 1))))

CL-USER 9 : 1 > (load "c:\\revcalc\\lisp\\test-out.lisp")
; Loading text file c:\revcalc\lisp\test-out.lisp
Error while reading: Subcharacter #\< not defined for dispatch char
#\#.

Based on what I've seen, the object itself is correct, which would
mean I am trying to write / read the output from make-load-form
incorrectly.  Any pointers would be very welcome.

Thanks,
From: Nils Goesche
Subject: Re: Help using make-load-form in LispWorks CL
Date: 
Message-ID: <87adbi49u9.fsf@darkstar.cartan>
······@schulte-llc.com (Thomas Schulte) writes:

> I hope I'm not running over well-traveled ground for this
> group, but I didn't see the answer to this question in previous
> posts.  I am trying to use make-load-form to save and later
> retrieve an object.
> 
> A simple class:
> 
> (defclass test ()
>   ((slot-a :initarg :a
>            :accessor a)))
> 
> (defmethod make-load-form ((self test) &optional environment)
>     (make-load-form-saving-slots self))
> 
> And how I am trying to use it:
> 
> CL-USER 6 : 1 > (setq obj1 (make-instance 'test :a 1)) 
> #<TEST 20622AF4>
> 
> CL-USER 7 : 1 > (make-load-form obj1)
> (CLOS::MLF-ALLOCATE-INSTANCE (QUOTE TEST))
> (CLOS::MLF-SET-INSTANCE-SLOTS #<TEST 20622AF4> (QUOTE ((SLOT-A . 1))))
> 
> CL-USER 8 : 1 > (with-open-file (file-out
> "c:\\revcalc\\lisp\\test-out.lisp" :direction :output :if-exists
> :supersede)
>                   (multiple-value-bind (a b) (make-load-form obj1)
>                     (prin1 a file-out)
>                     (prin1 b file-out)))
> (CLOS::MLF-SET-INSTANCE-SLOTS #<TEST 20622AF4> (QUOTE ((SLOT-A . 1))))
> 
> CL-USER 9 : 1 > (load "c:\\revcalc\\lisp\\test-out.lisp")
> ; Loading text file c:\revcalc\lisp\test-out.lisp
> Error while reading: Subcharacter #\< not defined for dispatch char
> #\#.
> 
> Based on what I've seen, the object itself is correct, which would
> mean I am trying to write / read the output from make-load-form
> incorrectly.  Any pointers would be very welcome.

I suspect you do not understand what MAKE-LOAD-FORM really does.
First, if you only want to be able to read/write objects, you do
not need MAKE-LOAD-FORM at all.  Consider

(defpackage "TEST"
  (:use "CL")
  (:export "TEST"))

(in-package "TEST")

(defclass test ()
  ((slot-a :initarg :slot-a :accessor slot-a)))

(defmethod get-slots-to-dump ((self test))
  '(slot-a))

;;; First define how to write objects of class TEST

(defmethod print-object ((self test) stream)
  (write-string "#{" stream)
  (write (class-name (class-of self)) :stream stream)
  (dolist (slot-name (get-slots-to-dump self))
    (when (slot-boundp self slot-name)
      (write-char #\Space stream)
      (write slot-name :stream stream)
      (write-char #\Space stream)
      (write (slot-value self slot-name) :stream stream)))
  (write-char #\} stream)
  self)

;;; Now define how to read them back in

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun test-reader (stream char arg)
    (declare (ignore char arg))
    (let* ((class-name (read stream t nil t))
           (slots (read-delimited-list #\} stream t))
           (obj (make-instance class-name)))
      (loop for (slot-name slot-value) on slots by #'cddr do
            (setf (slot-value obj slot-name) slot-value))
      obj))
  (set-syntax-from-char #\} #\))
  (set-dispatch-macro-character #\# #\{ #'test-reader))

;;; Now we use the newly defined syntax

(defparameter *test-proto* #{test slot-a 42})

If you put the above code in a file test.lisp, open it in the
editor and push whatever keys you use for ``compile and load��,
you can evaluate

TEST 13 > *test-proto*
#{TEST SLOT-A 42}

TEST 14 > (with-open-file (s "test.dump" :direction :output
                             :if-exists :supersede)
            (prin1 #{test slot-a 2000} s))
#{TEST SLOT-A 2000}

TEST 15 > (with-open-file (s "test.dump")
            (read s))
#{TEST SLOT-A 2000}

So, now you can read and write TEST objects.  However, there is
one thing that doesn't work yet:

TEST 16 > (compile-file "test.lisp")
;;; Compiling file test.lisp ...
;;; Safety = 3, Speed = 1, Space = 1, Float = 1, Interruptible = 0
;;; Compilation speed = 1, Debug = 2, Fixnum safety = 3, GC safety = 3
;;; Source level debugging is on 
;;; Source file recording is  on 
;;; Cross referencing is on
; (LISPWORKS:TOP-LEVEL-FORM 1)
; (DEFPACKAGE "TEST")
; (LISPWORKS:TOP-LEVEL-FORM 2)
; (DEFCLASS TEST)
; (METHOD GET-SLOTS-TO-DUMP (TEST))
; (METHOD PRINT-OBJECT (TEST T))
; TEST-READER
; (LISPWORKS:TOP-LEVEL-FORM 3)
; (LISPWORKS:TOP-LEVEL-FORM 4)


**++++ Error in (DEFVAR *TEST-PROTO*): 
  Do not know how to fasl-dump #{TEST SLOT-A 42}.
No user-defined "make-load-form" method.
; (LISPWORKS:TOP-LEVEL-FORM 5)
; *** 1 error detected, no fasl file produced.

;; *** Automatic Minor Clean Down
NIL
(((DEFVAR *TEST-PROTO*) #<SIMPLE-ERROR 206E41B4>))
T


See?  By saying

(defparameter *test-proto* #{test slot-a 42})

we make an object of type TEST part of the source code!  When
test.lisp is compiled by the file compiler, you must be able to
kill and restart your Lisp and then load the compiled FASL file
in a way that the object #{test slot-a 42} is valid and alive
again!  This is not trivial at all -- there is no general,
meaningful way of putting a live object from memory into a FASL
file in such a way that it will still be valid when the FASL file
is loaded into another Lisp image.  /You/ have to tell the file
compiler how a given instance of type TEST can be recreated at
load time.  For this, you use MAKE-LOAD-FORM.  So, we enhance the
above EVAL-WHEN form:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun test-reader (stream char arg)
    (declare (ignore char arg))
    (let* ((class-name (read stream t nil t))
           (slots (read-delimited-list #\} stream t))
           (obj (make-instance class-name)))
      (loop for (slot-name slot-value) on slots by #'cddr do
            (setf (slot-value obj slot-name) slot-value))
      obj))
  (set-syntax-from-char #\} #\))
  (set-dispatch-macro-character #\# #\{ #'test-reader)
  (defmethod make-load-form ((self test) &optional environment)
    (make-load-form-saving-slots self
                                 :slot-names (get-slots-to-dump self)
                                 :environment environment)))

Above, you also have to wrap GET-SLOTS-TO-DUMP into an EVAL-WHEN:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defmethod get-slots-to-dump ((self test))
    '(slot-a)))

Now you can call (compile-file "test.lisp") in a freshly
restarted Lisp and (load "test") any time you want.  Note that
you might not need to write GET-SLOTS-TO-DUMP yourself and could
use one of the MOP functions for this.  If you don't, the above
code will work for all classes you derive from TEST, if you
update GET-SLOTS-TO-DUMP for each of the new classes, too.

Hope this helps somewhat...

Regards,
-- 
Nils G�sche
Ask not for whom the <CONTROL-G> tolls.

PGP key ID #xD26EF2A0