

Issue: LOAD-OBJECTS (Version 3)

I was a little surprised to see that this proposal talks about load
forms instead of load functions (which goes to show how much I've been
paying attention). After some thought and consultation with Moon, I
realized that part of it was that compiled functions could not be
compiled constants. If we were to allow such constants, I would propose
we consider the alternative of load functions.

The model would be that when objects are being either prepared for
dumping or are being dumped, at certain points the generic function
MAKE-LOAD-FUNCTION would be invoked on objects that needed to be
re-created later.  It would return either one or two functions. The
first is a function of 0 arguments that does the initial creation, and
the second is (if present) a function of 1 argument, which is the
initializer. If present it is applied to the created instance. This
simplifies the naming problem in the current proposal, which, while
clever, is a little unpalatable. In particular, it introduces yet
another way to think about variables.

I think people will find the macro approach (the current approach)
baroque, partly because the approach is best understood by thinking of
an input phase to a compiler or some such program, rather than by
thinking about an output phase when everything has already been supposedly
created. For example, when I read the current proposal, I imagined it
in the FASDUMP phase.

One drawback of my proposal is that the function approach is a little
more verbose in some cases. I also think it is subject to more
circularity errors by novices than the macro approach.  On the other
hand, the functional approach makes one think about the issues a
little harder when writing the code, which is possibly a good thing.

Here are the examples in the macro proposal:

  ;; Example 1
  (defclass my-class ()
      (b :initarg :b :reader my-b)
      (c :accessor my-c)))
    (unless (slot-boundp self 'c)
  (defmethod make-load-function ((self my-class))
    (let ((name (class-name (class-of self)))
	  (a (my-a self))
	  (b (my-b self)))
      #'(lambda () (make-instance name :a a :b b))))

Here the computations of NAME, A, and B must be outside the function
#'(lambda ...) so that they get evaluated in the right environment to
avoid a circular (self-referential) structure. For this to work, the
faslout of #'(lambda ...) must also notice any constants or such
things that need similar treatment, which will get NAME, A, and B, if

  ;; Example 2
  (defclass my-frob ()
  (defmethod make-load-function ((self my-frob))
    (let ((name (my-name self)))
      #'(lambda () (find-my-frob name :if-does-not-exist :create))))

Maybe NAME is not something to worry about, but SELF cannot appear in
the #'(lambda ...).

  ;; Example 3 (expanded to do a hairy thing that cannot be easily done
  ;; in the macro approach).
				 (curious-facts :accessor tree-foma)
                                 (children :initarg :children)))
  (defmethod make-load-function ((x tree-with-parent))
    (let ((class (class-of x))
	  (children (slot-value x 'children))
	  (random-info-at-dump-time (compute-random-info x))
	  (more-random-info-at-creation-time ())
	  (parent (slot-value x 'parent)))
      (flet ((initialize (x)
	       (setf (tree-foma x)
		     (combine-info random-info-at-dump-time
	       (setf (tree-parent x) parent)))
	  ;; creation 
	  #`(lambda ()
	      (setq more-random-info-at-creation-time	      
	      (make-instance class :children children))
	  ;; initialization

One can imagine the shared lexical environment of the creator and initializer
being a high-bandwidth channel for information, such as the important
information passed in the above example.

Finally, I wouldn't use the name MAKE-LOAD-FUNCTION-USING-SLOTS,
because the structure of the name ...-USING-SLOTS is like
...-USING-CLASS, which is named that way to inform the programmer that
he can discriminate on the metaclass. Maybe,