From: Friedrich Dominicus
Subject: How to use make-load-form-saving-slots
Date: 
Message-ID: <87lmlpcqdu.fsf@frown.here>
I don't get around how to use the above function in a method
make-load-form
If I have this class:
(defclass test-class ()
  ((foo :initarg :foo :accessor foo)))

now I create an instance
(setf t1 (make-instance 'test-class :foo 100))


I can understand how my make-load-form should look to yield a form
which evaluated give me a copy
(defmethod make-load-form ((self test-class) &optional environment)
  (declare (ignore environment))
  `(make-instance ',(class-of self)
   :foo ',(foo self)))


If I try that
(setf t2 (eval (make-load-form t1)))
t2 will get created with the foo slot filled according to t1.

That's fine, now in the Hyperspec I find you can implement
make-load-form using make-load-form-saving-slots and that will yield
simular things. Now just applied I got
CL-USER 57 : 1 > (make-load-form-saving-slots t1)
(CLOS::MLF-ALLOCATE-INSTANCE (QUOTE TEST-CLASS))
(CLOS::MLF-SET-INSTANCE-SLOTS #<TEST-CLASS 222F1EE4> (QUOTE ((FOO
. 100))))

what I do not understand is the second element of the initialization
form that is the printed representation of the t1 object with address.

As I understand the first form creates an object of the approprate
class and the second form sets the slots, but doesn't that mean that
the first form should be the second element of the second form?

Now I tried to answer this question myself and come up with this:
(defmethod make-load-form((self test-class) &optional environment)
  (declare (ignore environment))
  (multiple-value-bind (cf if)
      (make-load-form-saving-slots self)
    (let ((obj (eval cf)))
      (setf (second if) obj)
      (eval if)
      obj)))

That seems to do the job
(setf t1 (make-instance 'test-class :foo 100))
(foo t1) -> 100
(setf t2 (make-load-form t2))
(foo t2) -> 100

However I do not think that I understand how to use the
make-load-forms properly. 

Can someone give me a hand please?

Regards
Friedrich

From: Friedrich Dominicus
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <87hewdcpxb.fsf@frown.here>
Friedrich Dominicus <·····@q-software-solutions.com> writes:

> 
> Now I tried to answer this question myself and come up with this:
> (defmethod make-load-form((self test-class) &optional environment)
>   (declare (ignore environment))
>   (multiple-value-bind (cf if)
>       (make-load-form-saving-slots self)
>     (let ((obj (eval cf)))
>       (setf (second if) obj)
>       (eval if)
>       obj)))
> 
> That seems to do the job
of giving me a copy but is definitly wrong. The thing which should be
given back by make-load-form is a form and so it's definitly wrong, so
I'm absolutly wrong about the usage of the make-load stuff.


Regards
Friedrich
From: Tim Bradshaw
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <ey3d7716lpe.fsf@cley.com>
* Friedrich Dominicus wrote:

> As I understand the first form creates an object of the approprate
> class and the second form sets the slots, but doesn't that mean that
> the first form should be the second element of the second form?

No, MAKE-LOAD-FORM may return two values: the first value is a form
which, when evaluated, will create a suitable object.  The second
form, if it returns one, should, when evaluated, initialise the
object.  Any occurrences of the argument to MAKE-LOAD-FORM in the
second return value are replaced by the newly-created object when it
is evaluated.

MAKE-LOAD-FORM-SAVING-SLOTS returns two forms in this way, one which
creates a suitable object, and one which initialises it.

The reason that you need two forms is to deal with circularities.
Consider this code:

(defclass grub ()
  ((grib :accessor grib
         :initform nil)))

(defvar *g* (let ((g1 (make-instance 'grub))
                  (g2 (make-instance 'grub)))
              (setf (grib g1) g2
                    (grib g2) g1)
              g1))

To recreate *G* there's a two stage process.  Firstly enough creation
forms are evaluated to make the objects that need to exist exist, then
the initialisation forms are evaluated to stitch them together.  This
is a slightly vague (well, very vague), description: the entry for
MAKE-LOAD-FORM in the spec describes what actually happens.

--tim
From: Pierre R. Mai
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <87elrh3thh.fsf@orion.bln.pmsf.de>
Friedrich Dominicus <·····@q-software-solutions.com> writes:

> I can understand how my make-load-form should look to yield a form
> which evaluated give me a copy
> (defmethod make-load-form ((self test-class) &optional environment)
>   (declare (ignore environment))
>   `(make-instance ',(class-of self)
>    :foo ',(foo self)))

But since you could also have circular dependencies, i.e. two
instances of test-class the one being the value of the foo slot of the
other and vice-versa, you really should be following the two-step
protocol of allocation and initialization, e.g.:

(defmethod make-load-form ((self test-class) &optional environment)
  (values
   `(make-instance ',(class-of self))
   `(setf (slot-value ',self 'foo) ',(slot-value self 'foo))))

The occurrence of the object itself in the second form will be
replaced at load-time by the newly created object.  This is special
behaviour the file-loader will do for you, and is specified in the
entry for make-load-form in the standard.

> CL-USER 57 : 1 > (make-load-form-saving-slots t1)
> (CLOS::MLF-ALLOCATE-INSTANCE (QUOTE TEST-CLASS))
> (CLOS::MLF-SET-INSTANCE-SLOTS #<TEST-CLASS 222F1EE4> (QUOTE ((FOO
> . 100))))
> 
> what I do not understand is the second element of the initialization
> form that is the printed representation of the t1 object with address.
> 
> As I understand the first form creates an object of the approprate
> class and the second form sets the slots, but doesn't that mean that
> the first form should be the second element of the second form?

No, as I said above, every occurrence of the object being saved itself
is replaced in the forms you return at load-time by the newly created
object, i.e. the value obtained by evaluating the first form you
return.  This special magic is required in order to allow the loader
to first allocate all objects, and only then initialize them in the
second step, in order to recreate circular-dependencies, etc.

> Can someone give me a hand please?

Hope this aids your understanding...

Regs, Pierre.

-- 
Pierre R. Mai <····@acm.org>                    http://www.pmsf.de/pmai/
 The most likely way for the world to be destroyed, most experts agree,
 is by accident. That's where we come in; we're computer professionals.
 We cause accidents.                           -- Nathaniel Borenstein
From: Friedrich Dominicus
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <87zoa4q2sr.fsf@frown.here>
"Pierre R. Mai" <····@acm.org> writes:

> 
> Hope this aids your understanding...
Not fully, so how do I use it than. What do I have to save and what do
I have to load then? Just a simple example, if I have the object t1
with foo = 100. Then how do I save it and how to I have to reload it?

Regards
Friedrich
From: Pierre R. Mai
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <87zoa4zu5y.fsf@orion.bln.pmsf.de>
Friedrich Dominicus <·····@q-software-solutions.com> writes:

> "Pierre R. Mai" <····@acm.org> writes:
> 
> > 
> > Hope this aids your understanding...
> Not fully, so how do I use it than. What do I have to save and what do
> I have to load then? Just a simple example, if I have the object t1
> with foo = 100. Then how do I save it and how to I have to reload it?

See the example method for MAKE-LOAD-FORM in my message.  That's how
you might do it, IF you decided you wanted to do it by hand.  OTOH,
unless your requirements are very specific, using
MAKE-LOAD-FORM-SAVING-SLOTS is the easier, and possibly more efficient
approach.  E.g. given

(defclass test-class ()
  ((foo :accessor test-class-foo)
   (bar :accessor test-class-bar :initform 0)))

if you want to save all slots, then define

(defmethod make-load-form ((object test-class) &optional environment)
  (make-load-form-saving-slots object :environment environment))

if you only want to save the foo slot, then define

(defmethod make-load-form ((object test-class) &optional environment)
  (make-load-form-saving-slots object :slot-namess '(foo) 
                               :environment environment))

That's all you need to do, unless you need much more complex
behaviour, in which case you'll have to do it by hand in
make-load-form.

Does that answer your question, or are you asking something
differently?

Regs, Pierre.

-- 
Pierre R. Mai <····@acm.org>                    http://www.pmsf.de/pmai/
 The most likely way for the world to be destroyed, most experts agree,
 is by accident. That's where we come in; we're computer professionals.
 We cause accidents.                           -- Nathaniel Borenstein
From: Friedrich Dominicus
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <878zhkt3j1.fsf@frown.here>
"Pierre R. Mai" <····@acm.org> writes:
> Does that answer your question, or are you asking something
> differently?
Now I do probably misunderstand what make-load-form does. Guess I have
this

(defclass test-class ()
  ((foo :initarg :foo :accessor foo)))



(defvar *t-lists* '())

*t-lists* should contain objects of type test-class. This objects will
be created during run time, and pushed onto that list.

Now assume the list was filled:
(defun fill-list ()
  (push (make-instance 'test-class :foo 100) *t-lists*)
  (push (make-instance 'test-class :foo 200) *t-lists*)
 (push (make-instance 'test-class :foo 300) *t-lists*))

The values are unimporatant just to show what I mean.

Now during run time a new instance will be created and pushed onto that
list. It now contains 4 elements. I now wanted to make this list
persistent and next time I load the system the list gets
reconstructed. I was thinking that I do have to use make-load-form for
that. e.g

(dolist (item *t-lists*)
   (make-load-form item)

write the forms to disk and then in the next run. I would say
reconstruct-*t-lists*) and got a new list with a fresh generated
elements of type test-class.

I'm obviously missing the point, and therefor I'm stuck.

Regards
Friedrich
From: Kent M Pitman
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <sfwae1zkghl.fsf@world.std.com>
Friedrich Dominicus <·····@q-software-solutions.com> writes:

> Now I do probably misunderstand what make-load-form does. 
> Guess I have this
> 
> (defclass test-class () ((foo :initarg :foo :accessor foo)))
> (defvar *t-lists* '())
> 
> *t-lists* should contain objects of type test-class. This objects will
> be created during run time, and pushed onto that list.
> 
> Now assume the list was filled:
> (defun fill-list ()
>   (push (make-instance 'test-class :foo 100) *t-lists*)
>   (push (make-instance 'test-class :foo 200) *t-lists*)
>   (push (make-instance 'test-class :foo 300) *t-lists*))
> The values are unimporatant just to show what I mean.
> 
> Now during run time a new instance will be created and pushed onto that
> list. It now contains 4 elements. I now wanted to make this list
> persistent and next time I load the system the list gets
> reconstructed. I was thinking that I do have to use make-load-form for
> that. e.g
> (dolist (item *t-lists*)
>    (make-load-form item)
> write the forms to disk and then in the next run. I would say
> reconstruct-*t-lists*) and got a new list with a fresh generated
> elements of type test-class.
> 
> I'm obviously missing the point, and therefor I'm stuck.

You're misunderstanding what MAKE-LOAD-FORM does.  It implements the missing
piece of functionality that allows Lisp to externalize an object.   (Similar
to serialization, but neutral as to both I/O and data format.)

MAKE-LOAD-FORM will be called by the fasl file (i.e., lisp binary file)
maker when trying to dump a literal constant and will return a piece of 
code that can be dumped in order to create the object.  That code is then
dumped into the fasl file.

Having done a MAKE-LOAD-FORM definition for a TEST-CLASS, you can now
do several things.  You can file compile code that contains literal
references to your object. e.g.,
 (defun foo () '#.(first *t-lists*))
will evaluate *t-lists* at read-time, find its first element, and become a
function that returns that literal object.  It may even be that the object
doesn't print/read invertibly (for that you have to deal with a print-object
method and a readmacro syntax), but it can still be compiled and reloaded.

If the object is circular (or potentially so), you'll probably have a problem
detecting and handling that.  So that's why two values are returned.

You can get examples in CLHS.

Note that in your DOLIST above, you expect MAKE-LOAD-FORM to do a side-effect,
which it does not.  You COULD get the result of it (you'd probably want
to pick up both values, btw) and use PRINT to put them to a file textually.
But you didn't, so you won't get any effect.

Or you could just do something like:

 (compile-file
   (with-open-file (stream "temporary-file-for-demo.lisp" 
  		           :direction :output
		           :if-exists :supersede)
     (print '(in-package "CL-USER") stream)
     (print `(defvar *t-lists* ',*t-lists*) stream)
     (truename stream)))

The MAKE-LOAD-FORM would be used implicitly by COMPILE-FILE to dump
the literal constant denoted in ',*t-lists*
From: Friedrich Dominicus
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <87lmljsrl7.fsf@frown.here>
Kent M Pitman <······@world.std.com> writes:

> 
> You're misunderstanding what MAKE-LOAD-FORM does.
Definitly yes.

>It implements the missing
> piece of functionality that allows Lisp to externalize an object.   (Similar
> to serialization, but neutral as to both I/O and data format.)
> 
> MAKE-LOAD-FORM will be called by the fasl file (i.e., lisp binary file)
> maker when trying to dump a literal constant and will return a piece of 
> code that can be dumped in order to create the object.  That code is then
> dumped into the fasl file.
> 
> Having done a MAKE-LOAD-FORM definition for a TEST-CLASS, you can now
> do several things.  You can file compile code that contains literal
> references to your object. e.g.,
>  (defun foo () '#.(first *t-lists*))
> will evaluate *t-lists* at read-time, find its first element, and become a
> function that returns that literal object.  It may even be that the object
> doesn't print/read invertibly (for that you have to deal with a print-object
> method and a readmacro syntax), but it can still be compiled and reloaded.
> 
> If the object is circular (or potentially so), you'll probably have a problem
> detecting and handling that.  So that's why two values are returned.
> 
> You can get examples in CLHS.
I checked the examples over and over again but I did not understand
it, that was and probably is my problem.
> 
> Note that in your DOLIST above, you expect MAKE-LOAD-FORM to do a side-effect,
> which it does not.  You COULD get the result of it (you'd probably want
> to pick up both values, btw) and use PRINT to put them to a file textually.
> But you didn't, so you won't get any effect.
I was sure that what I was trying to do was false. I tried to explaind
where my problems have been.
> 
> Or you could just do something like:
> 
>  (compile-file
>    (with-open-file (stream "temporary-file-for-demo.lisp" 
>   		           :direction :output
> 		           :if-exists :supersede)
>      (print '(in-package "CL-USER") stream)
>      (print `(defvar *t-lists* ',*t-lists*) stream)
>      (truename stream)))
> 
> The MAKE-LOAD-FORM would be used implicitly by COMPILE-FILE to dump
> the literal constant denoted in ',*t-lists*
I think that is what I reallly need and I'll try it now

Thanks for explaining what I did not get.

Friedrich
From: Friedrich Dominicus
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <87hew7sqn9.fsf@frown.here>
Kent M Pitman <······@world.std.com> writes:

> 
> Or you could just do something like:
> 
>  (compile-file
>    (with-open-file (stream "temporary-file-for-demo.lisp" 
>   		           :direction :output
> 		           :if-exists :supersede)
>      (print '(in-package "CL-USER") stream)
>      (print `(defvar *t-lists* ',*t-lists*) stream)
>      (truename stream)))

Could you show what I have to do do get that running?
assuming *t-lists* contains
an object with (= (foo obj1) 100)
and (= (foo obj2) 200)

This is what I wanted to use for make-load-form
(defmethod make-load-form((self test-class) &optional environment)
  (declare (ignore environment))
(make-load-form-saving-slots self))

the printable form of an object looks like this here
CL-USER 87 : 4 > (setf foo-1 (make-instance 'test-class :foo 100))
#<TEST-CLASS 204DE8CC>

Now for the simplicicty of my mind assume
(setf foo-2 (make-instance 'test-class :foo 200))

(push foo-1 *t-lists*)


CL-USER 93 : 5 > (push foo-2 *t-lists*)
(#<TEST-CLASS 2050A094> #<TEST-CLASS 204DE8CC>)


Of course that can not be dumped, or it can but than not re-read.
Now what I want is simply beeing able to dump that list and of the
objects in it and let them reconstruct while loading. That seems to me
what make-load-form should do. Again it may be that I'm simply blind
to see what happened here.

Thanks
Friedrich
From: Kent M Pitman
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <sfwelrbtkqk.fsf@world.std.com>
Friedrich Dominicus <·····@q-software-solutions.com> writes:

> Kent M Pitman <······@world.std.com> writes:
> 
> > 
> > Or you could just do something like:
> > 
> >  (compile-file
> >    (with-open-file (stream "temporary-file-for-demo.lisp" 
> >   		           :direction :output
> > 		           :if-exists :supersede)
> >      (print '(in-package "CL-USER") stream)
> >      (print `(defvar *t-lists* ',*t-lists*) stream)
> >      (truename stream)))
> 
> Could you show what I have to do do get that running?
> assuming *t-lists* contains
> an object with (= (foo obj1) 100)
> and (= (foo obj2) 200)

Actually, I just screwed up what I wrote.  You can do it without print-object
methods by doing:

(compile-file
  (with-open-file (stream "temporary-file-for-demo.lisp" 
 		           :direction :output
		           :if-exists :supersede)
    (format stream "(in-package \"CL-USER\")~
                  ~%(defparameter *t-lists* '#.*t-lists*)~%")
    (truename stream)))

Note (1) I changed it to defparameter, so it will actually assign
         (and clobber) *t-lists* if it already has a value; you
         might or might not want this; you might want to change
         it back to defvar.
     (2) I used '#.*t-lists* in the output, which will not print the
         value into the file but will rely on the compiler to have the
         relevant value already assigned and dynamically acquired.

To see the real effect of this, try:

 (defclass test-class () ((foo :initarg :foo :accessor foo :initform 0)))
 (defmethod make-load-form ((test-class test-class) &optional environment)
   `(make-instance 'test-class :foo ',(foo test-class)))
 (defvar *t-lists* '())
 (push (make-instance 'test-class :foo 100) *t-lists*)
 (push (make-instance 'test-class :foo 200) *t-lists*)
 (let ((compiled-file 
	 (compile-file
	   (with-open-file (stream "temporary-file-for-demo.lisp" 
				    :direction :output
				    :if-exists :supersede)
	     (format stream "(in-package \"CL-USER\")~
			   ~%(defparameter *t-lists* '#.*t-lists*)~%")
	     (truename stream)))))
   (setq *old-t-lists* *t-lists*)
   (setq *t-lists* '())
   (load compiled-file)
   (print *t-lists*))
From: Friedrich Dominicus
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <87wv52lshf.fsf@frown.here>
Kent M Pitman <······@world.std.com> writes:


> 
> To see the real effect of this, try:
> 
>  (defclass test-class () ((foo :initarg :foo :accessor foo :initform 0)))
>  (defmethod make-load-form ((test-class test-class) &optional environment)
>    `(make-instance 'test-class :foo ',(foo test-class)))
>  (defvar *t-lists* '())
>  (push (make-instance 'test-class :foo 100) *t-lists*)
>  (push (make-instance 'test-class :foo 200) *t-lists*)
>  (let ((compiled-file 
> 	 (compile-file
> 	   (with-open-file (stream "temporary-file-for-demo.lisp" 
> 				    :direction :output
> 				    :if-exists :supersede)
> 	     (format stream "(in-package \"CL-USER\")~
> 			   ~%(defparameter *t-lists* '#.*t-lists*)~%")
> 	     (truename stream)))))
>    (setq *old-t-lists* *t-lists*)
>    (setq *t-lists* '())
>    (load compiled-file)
>    (print *t-lists*))
Thanks Ken. Now one of those examples in the Hyperspec would be very
very helpful I simply did not understand how the different things
"play" together. The examples just stop after a definition of the
make-load-from. Of course it shows what make-load-form does, but if I
look over your code I see :


(defmethod make-load-form ((test-class test-class) &optional environment)
  `(make-instance 'test-class :foo ',(foo test-class)))

Now this can be used to make a copy of that object by just using eval

(CL-USER 10 : 1 > (setf foo-1 (make-instance 'test-class :foo 100))
#<TEST-CLASS 22365394>

CL-USER 11 : 1 > (make-load-form foo-1)
(MAKE-INSTANCE (QUOTE TEST-CLASS) :FOO (QUOTE 100))

CL-USER 12 : 1 > (setf foo-2 (eval (make-load-form foo-1)))
#<TEST-CLASS 22372A94>

CL-USER 13 : 1 > (foo foo-2)
100

CL-USER 14 : 1 >

Now that does not work obviously the same way for
making-load-form-saving-slots. But if I keep my classes simple (as I
intended to do for the thing I'm going to do) , the latter function does all
the "things automagically". Of course it's not terrible difficult to
implement a suitable Method, but if things are done for me I will let
them do that. So in this case and even for some more slots I do think
I prefer using make-load-form-saving-slots.

Anyway what was missing for me is how to use it correctly. Thanks for
taking the time showing me that step by step.

Regards
Friedrich
From: Pierre R. Mai
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <87puauo4q3.fsf@orion.bln.pmsf.de>
Friedrich Dominicus <·····@q-software-solutions.com> writes:

> Thanks Ken. Now one of those examples in the Hyperspec would be very
> very helpful I simply did not understand how the different things
> "play" together. The examples just stop after a definition of the
> make-load-from. Of course it shows what make-load-form does, but if I
> look over your code I see :
> 
> 
> (defmethod make-load-form ((test-class test-class) &optional environment)
>   `(make-instance 'test-class :foo ',(foo test-class)))
> 
> Now this can be used to make a copy of that object by just using eval
> 
> (CL-USER 10 : 1 > (setf foo-1 (make-instance 'test-class :foo 100))
> #<TEST-CLASS 22365394>
> 
> CL-USER 11 : 1 > (make-load-form foo-1)
> (MAKE-INSTANCE (QUOTE TEST-CLASS) :FOO (QUOTE 100))
> 
> CL-USER 12 : 1 > (setf foo-2 (eval (make-load-form foo-1)))
> #<TEST-CLASS 22372A94>
> 
> CL-USER 13 : 1 > (foo foo-2)
> 100
> 
> CL-USER 14 : 1 >

That only works "by accident", because no initialization form has been
returned.

> Anyway what was missing for me is how to use it correctly. Thanks for
> taking the time showing me that step by step.

MAKE-LOAD-FORM isn't really intended to be used directly by you, you
can look at it as a call-back from the implementation (the FASL file
writer in particular) to your code,  so that you can tailor what parts
of an object are externalized, and how the object is put back together
at load time.

This is similar to e.g. UPDATE-INSTANCE-FOR-REDEFINED-CLASS, which
also isn't intended to be called by programmers, but rather is called
by the implementation in certain circumstances.

That being said, you are free to call MAKE-LOAD-FORM for certain
objects:

<quote>
Conforming programs may call make-load-form directly, providing object is a
generalized instance of standard-object, structure-object, or condition.
</quote>

So you might try to reproduce the behaviour of the FASL writer with
code of your own.  But that is a lot of work, since you will have to
process the forms returned quite a bit, both at write as well as read
time (e.g. arranging for the newly allocated object to be inserted
into the initialization form everywhere the old object was present at
write time, etc.).  The additional problem is that you are not allowed
to call make-load-form for e.g. BUILTIN-OBJECTS, so you might be
unable to externalize certain complex objects, which don't have a
readable print syntax, _AND_ where you aren't allowed to call
MAKE-LOAD-FORM like e.g. HASH-TABLEs.  Note that you have to do your
own form walking in order to recursively call all the MAKE-LOAD-FORM
methods needed.  There are many things to look out for, and it is IMHO
much better to leave that to implementors, than to try to do it
yourself. 

So your best course of action would be to either rely on the portable
COMPILE-FILE way of externalization, as demonstrated by Kent, or to
find out if your implementation gives you direct access to its FASL
file writer, which might provide more convenient routines for
externalizing objects.

The one thing that's not very nice about COMPILE-FILE is that it
doesn't work on streams instead of files, hence you need a temporary
file for the source.  But other than that it should do everything you
want.

Regs, Pierre.

-- 
Pierre R. Mai <····@acm.org>                    http://www.pmsf.de/pmai/
 The most likely way for the world to be destroyed, most experts agree,
 is by accident. That's where we come in; we're computer professionals.
 We cause accidents.                           -- Nathaniel Borenstein
From: Friedrich Dominicus
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <87ofqde9vr.fsf@frown.here>
"Pierre R. Mai" <····@acm.org> writes:

> Friedrich Dominicus <·····@q-software-solutions.com> writes:
> 
> > Thanks Ken. Now one of those examples in the Hyperspec would be very
> > very helpful I simply did not understand how the different things
> > "play" together. The examples just stop after a definition of the
> > make-load-from. Of course it shows what make-load-form does, but if I
> > look over your code I see :
> > 
> > 
> > (defmethod make-load-form ((test-class test-class) &optional environment)
> >   `(make-instance 'test-class :foo ',(foo test-class)))
> > 
> > Now this can be used to make a copy of that object by just using eval
> > 
> > (CL-USER 10 : 1 > (setf foo-1 (make-instance 'test-class :foo 100))
> > #<TEST-CLASS 22365394>
> > 
> > CL-USER 11 : 1 > (make-load-form foo-1)
> > (MAKE-INSTANCE (QUOTE TEST-CLASS) :FOO (QUOTE 100))
> > 
> > CL-USER 12 : 1 > (setf foo-2 (eval (make-load-form foo-1)))
> > #<TEST-CLASS 22372A94>
> > 
> > CL-USER 13 : 1 > (foo foo-2)
> > 100
> > 
> > CL-USER 14 : 1 >
> 
> That only works "by accident", because no initialization form has been
> returned.
Why does the eval stuff work here by accident. You get a form returned
which encapsulates the call to make-instance. I do not have to call it
make-load-form, but make-copy-from or the like. Now that should work
as long as there are not other objects to construct. But recursive
applied I may get a deep copy from an object. As I understand there is
no such facility in Common Lisp at the moment and you have to provide
it yourself. But that seems to be at least a starting point. What
anyway was importortant for me was that I misread and misunderstood
the Description of it fully. And believe it or not I did not found any
full example of it's usage...

Regards
Friedrich
From: Kent M Pitman
Subject: Re: How to use make-load-form-saving-slots
Date: 
Message-ID: <sfwn15xynsq.fsf@world.std.com>
Friedrich Dominicus <·····@q-software-solutions.com> writes:

> "Pierre R. Mai" <····@acm.org> writes:
> 
> > Friedrich Dominicus <·····@q-software-solutions.com> writes:
> > 
> > > ...
> > > Now this can be used to make a copy of that object by just using eval
> > > ...
> > 
> > That only works "by accident", because no initialization form has been
> > returned.
>
> Why does the eval stuff work here by accident.

It's much, much worse than Pierre says.

You're relying on your own implementation of methods for certain classes and
assuming (falsely) that others will adhere to your needs.

It's not just an issue of dataflow, the actual definition of MAKE-LOAD-FORM
is not what you think.  For example, consider the definition:

 (defvar *canonical-token-table* ;for a pseudo-INTERN action
   (make-hash-table :test 'equal))

 (defvar *using-find-token* nil)

 (defclass token ()
   ((name :initarg :name :accessor name)))

 (defmethod initialize-instance :before ((token token) &key)
   (unless *using-find-token*
     (error "You must use FIND-TOKEN to make a token.")))

 (defmethod find-token ((name token)) token)

 (defmethod find-token ((name string))
   (or (gethash name *canonical-token-table*)
       (let ((*using-find-token* t))
         (setf (gethash name *canonical-token-table*)
               (make-instance 'token :name name)))))

 (defmethod make-load-form ((token token) &optional environment)
   `(find-token ,(name token)))

Note here that doing (make-load-form (find-token "FRED")) will *not*
copy the token "FRED".  MAKE-LOAD-FORM's job is *not* to return a 
MAKE-INSTANCE form, that is just a common use.  Its job is to return
something that will reconstruct the object accurately.  And constructing
an interned token means not only building but interning the token.

Now, you could have (as we do with symbols) a COPY-TOKEN for making
uninterned tokens. 

 (defun copy-token (name)
   (let ((*using-find-token* t)) ;bypass error checking
     (make-instance 'token :name name)))

But then you'd also have to change the make-load-form to do:

 (defmethod make-load-form ((token token) &optional environment)
   (with-slots (name) token
     (if (eq token (find-token name))
       `(find-token ,name))
       `(copy-token ,name)))

If you want a function whose job it is to copy objects generally,
though, there is none.  For a detailed discussion of why, see my
"Parenthetically Speaking" article about "intentional" issues related
to equality and copy:
 http://world.std.com/~pitman/PS/EQUAL.html

> You get a form returned which encapsulates the call to make-instance.

As I hope I've shown, you only get this accidentally in your special case.

> I do not have to call it make-load-form, but make-copy-from or the like.

It is necessary to build such new functionality on solid foundation.  EVEN
if you knew for a specific class that this was the right thing, you should
have a generic MAKE-COPY-FORM and build your MAKE-LOAD-FORM on that rather
than vice versa since it is reasonable to believe that sometimes making a
load form is rightly done by making a copy, but it is not reasonable to 
assume philosophically that making a copy is done by anything other than 
a coincidental application of something not intended for producing a copy.

> Now that should work
> as long as there are not other objects to construct.

No, there are a lot more issues than the dataflow.

> But recursive applied I may get a deep copy from an object.

The issue of deep and shallow copies oversimplify the nature of copying.
Consider conses, lists, alists, etc.  There are levels of depth other
than deep, and there are deep levels that ought not be infinite.
See my cited article above.

> As I understand there is
> no such facility in Common Lisp at the moment and you have to provide
> it yourself.

This is correct.

> But that seems to be at least a starting point. What
> anyway was importortant for me was that I misread and misunderstood
> the Description of it fully. And believe it or not I did not found any
> full example of it's usage...

Sorry about that.  I suppose that's my fault as editor.  There was a to-do
list a mile long of things that never got done.  We just had to cut off work
on the standard and call it done at a certain calendar time.