[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Reinitialization
I think Moon's summary was illuminating. Let me try to respond to several of
the issues.
(5) Do we really need four different updating functions? I
think the answer is yes, because user-defined methods could quite
plausibly do different things for each one. The only case I'm not
sure of is whether class-changed and update-instance-structure
really need to be distinguished by user-defined methods. Maybe
they are only distinct because they take different arguments now.
(6) Why do the four updating functions have such inconsistent
interfaces? It makes sense for two of them to take initialization
arguments and the other two to take before & after instance states,
so the real issue is why don't class-changed and
update-instance-structure take similar arguments? I believe that is
only an artifact of the way CLOS evolved; when we abandoned the
attempt to maintain a complete model of class redefinition history,
we also stopped passing a copy of the instance with its old
structure to update-instance-structure. That was because we didn't
have any class to use for that instance. However, just as
class-changed gets a temporary instance with potentially dynamic
extent, update-instance-structure could get a temporary instance of
a temporary class. That would be good, because the information
about what slots were added and removed could be accessed through
the normal Chapter 3 class examination functions, instead of
through special kludgey property lists, and the values of slots
could be accessed the normal way.
Although I am reluctant to change Chapters 1 and 2, I think it
would be justified to make update-instance-structure take two
instances as arguments, the same as class-changed. We should also
change the name of update-instance-structure to make it more
analogous (e.g. class-redefined). It would probably also be okay to
get rid of the distinction between class-changed and
update-instance-structure and just have one generic function for
both, although I haven't thought out the implications of that. We
should be real careful here, as we did think all this stuff out
moderately carefully and we may all have forgotten the reasons by
now.
The forgotten reasons had to do with how one could specify an obsolete class
with the appropriate methods for the old instance. To take our old favorite
example, suppose we wanted to change the definition of the class point. We
start with:
(defclass point ()
((x :accessor point-x :initarg :x)
(y :accessor point-y :initarg :y)))
(defmethod point-rho ((p point)) ;;compute rho from x and y)
(defmethod (setf point-rho) (new-rho (p point)) ;;compute new x and y)
(defmethod point-theta ((p point)) ;;compute theta from x and y)
(defmethod (setf point-theta) (new-theta (p point)) ;;compute new x and y)
We make a few instances and change it to:
(defclass point ()
((rho :accessor point-rho)
(theta :initarg point-theta)))
(defmethod point-x ((p point)) ;;compute x from rho and theta)
(defmethod (setf point-x) (new-x (p point)) ;;compute new rho and theta)
(defmethod point-y ((p point)) ;;compute y from rho and theta)
(defmethod (setf point-y) (new-y (p point)) ;;compute new rho and theta)
(defmethod initialize-instance :after ((p point)) &key :x :y)
;; if x and y are provided, use (setf point-x) (setf point-y)
;; to compute the appropriate values for rho and theta
)
Now, what methods remain on the class for old-point. We discussed
copying (using) the old methods. But we bogged down in trying to understand how
many other methods had to be copied in order to keep the original contract for
point-rho and point-theta. If point-rho called the generic function
(distance x y)
and we later decided to change the name of this function to
(euclidian-distance x y),
what would have to happen for update-instance-structure to work on the old
methods.
We decided that there was no reasonable way to specify what it meant to keep
around a copy of an obsolete-class and its methods, and therefore we had to use
the list descriptions of the old instance.
On the other hand, for class-changed, the methods and classes are current. Hence
it is reasonable to leave in the users hands the problem of keeping the method
for class-changed current.
---
(2) What should be shared among the four updating functions?
There are actually three different things:
(a) the code to fill slots from initialization arguments
(b) the code to fill slots from values of initialization forms
(c) user-defined methods
So far the discussion does not seem to have recognized that we
are talking about three distinct things that could be shared. The
thing we are talking about sharing is -not- simply the primary
method for initialize-instance.
Now, (a) only makes sense for initialize and reinitialize,
since the other two don't have initialization arguments. (b)
doesn't make sense for reinitialize in my opinion, and should only
be done for newly added slots for class-changed and
update-instance-structure, in my opinion (see issue 4). (c)
definitely makes sense for all four, except that only initialize
and reinitialize would pass initialization arguments to the
user-defined methods. By the way, (c) is what Barry Margolin
brought up at X3J13, so that's committee input to which we must
respond.
I want to argue, that under some control, all these make sense for all four
updating functions. The control is simply the list-of-slots-to-consider
argument that Moon proposed for initialize-slots-from-initforms. I will even be
so bold as to suggest some new names, though I would not like to defend them at
great cost.
The four updating functions are:
initialize-new-instance instance &rest initialization-arguments
reinitialize-instance instance &rest initialization-arguments
update-instance-structure instance added-slots discarded-slots property-list
&rest initialization-arguments
update-instance-with-new-class previous current
&rest initialization-arguments
These all call:
initialize-instance instance slots-for-initform
&rest initialization-arguments
The value of slots-for-initform can be a list of slot-names (possibly nil) or T,
which is equivalent to specifying all slots of the instance. For each slot on
the list, if it is uninitialized, and it has an initfunction, it is initialized
with the value of applying the initfunction to NIL.
What value does each of each of the updating functions pass to
initialize-intance for this argument.
The call from initialize-new-instance passes T of course.
The call from reinitialize-instance passes NIL
The call from update-instance-structure passes added-slots
The call from update-instance-with-new-class passes new-slots
Gregor and I previously argued that reinitialize-instance should try to
reinitialize all the slots that were unititialized. We claimed that this would
allow a specialized reinitialization method to call slot-makunbound on just
those slots wanted reinitialized. However, achieving this is still easy. The
specialized method still makes these slots unbound, and passes along the list of
slots that have been made unbound to initialize-instance.
All four pass the initialization-arguments to initialize-instance.
The call to update-instance-structure from the system of course passes NIL as
the initialization-arguments. So does the call to
update-instance-with-new-class. However, an :around method on either of these
latter can compute some interesting initialization-arguments and do a
call-next-method.
As an example of the use, consider the following around method for
update-instance-structure for point the above example:
(defmethod update-instance-structure :around ((p point) ...
;;push :x and :y on the initarg list and call-next-method
)
There is an obvious corresponding example for update-instance-with-new-class.
The upshot is that there is one shared generic function for all four updaters.
Code that is not to be shared is put in the updaters. Code that is to be shared
is put in the shared function. Updating from initforms and from
initialization-arguments can be controlled by what is passed to the shared code.