[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Redefining Classes
- To: Common-Lisp-Object-System@Sail.stanford.edu
- Subject: Redefining Classes
- From: David A. Moon <Moon@STONY-BROOK.SCRC.Symbolics.COM>
- Date: Thu, 24 Sep 87 23:35 EDT
- In-reply-to: <870924-091259-15052@Xerox>
Date: 24 Sep 87 09:12 PDT
From: Bobrow.pa@Xerox.COM
Redefining Classes
This is okay for the most part, but I have a few comments to offer
on excerpts from your proposal. There are a few areas that need to
be tightened up, plus one major defect. I'll conclude with a somewhat
random comment about multiprocess systems.
If the redefinition of a class causes the structure of its instances to
be different...
I think we're going to have to define this more precisely. I see two
options:
(1) Adding or removing an instance slot definitely changes the structure,
and some other operations may also change the structure, depending on the
implementation.
(2) The structure changes if and only if the value of
(class-all-instance-slot-names class) is not EQUAL to its previous value.
I made up the function name class-all-instance-slot-names, but you get
the idea. Note how class-all-slot-names won't work.
The big difference between option 1 and option 2 is that with option 2,
a given sequence of defclass forms will produce exactly the same
sequence of calls to make-instances-obsolete in all implementations.
I'm inclined to option 2, because it is more precise, but I'm worried
that this might rule out some implementation possibility. Also, for
this to be meaningful and portable, we have to define the exact order of
slots produced by slot inheritance, which is not something we've had to
specify before now.
Obsolete instance updating is done in two stages:
1) The system creates a property list that captures the slot names and
values of all the slots of the obsolete instance. It then transforms
the structure of the instance, so that it conforms to the current class
definition. All the slots of this transformed instance are
uninitialized. This first stage cannot be seen by user programs.
2) The generic function update-obsolete-instance is called. Its
arguments are the transformed instance, and the property list of
slot-names and values corresponding to the state of the obsolete
instance. Its contract is to do appropriate updating of the instance,
using the values from the obsolete slots.
I have two problems with this. The easy problem is that I don't like
the name update-obsolete-instance, because the instance doesn't really
seem obsolete to me. One idea is to take an analogy to class-changed
and call it class-redefined. In Flavors we call it transform-instance,
but it doesn't have quite the same semantics. It's likely that someone
can think of a better name than any of these.
Much more important, I can't implement the specification that the slots
are initially uninitialized and then they are filled in from values
saved in a property list. There are two problems, one obvious and one
subtle. The obvious problem involves slots that were unbound originally;
since there is no Lisp value that means "unbound", there is nothing that
can be put into the property list for these slots. But if we don't put
any entry in the property list, then the desired feature that the method
can tell which slots were added or removed is lost; these slots look like
they were added, even though they weren't. Also, the default method you
mention below will put the :initform value into these previously unbound
slots, which seems wrong. "Unbound" should not be confounded with
"nonexistent."
The subtle problem involves some language extensions that we have,
allowing slots to have contents that cannot be expressed as Lisp values
in a property list. For instance, there is a mechanism similar to
Prolog logic-variables which allows two slots to be linked together.
This linkage has to be preserved in the face of class redefinition.
For these reasons, I believe it is better to specify that the slot
values are conveyed from the old structure of the instance to the new
structure by the low-level implementation, not by a user-replaceable
method. Thus when the generic function update-obsolete-instance is
called, all slots that existed in the old class definition, and
existed as instance slots in the new class definition, already have
their values, as the same low-level bits. I can't see any need for
even a meta-user to replace this with something else.
Now we have to ask what information the update-obsolete-instance method
needs. If I remember last week's discussion, the idea of passing in a
property list was that it contained the set of old slots, and by querying
the class one could get the set of new slots, and thus the added and
deleted slots could be determined. Unfortunately unbound slots break this,
since either they can't have property list entries or the property list
entry has to contain a made-up value. I suggest conveying the desired
information directly, rather than trying to be clever: Give
update-obsolete-instance four arguments:
the instance
a list of the names of all added slots
a list of the names of all deleted slots
a property list giving the values of all deleted, bound slots
I think this conveys all relevant information directly, and it's
probably less consing than the full property list.
Because update-obsolete-instance is user code, its operation can be seen
by the user; but the model that CLOS supports is that as far as the user
can tell, all the existing instances of a class are updated as soon as
the class redefinition happens. This implies that the user must define
a method for update-obsolete-instance before redefining a class.
Implementations are free to delay the conversion of existing instances
(for example, to method call or slot access time), but users should
never be able to see the untransformed instance.
We're going to have to specify this a little more precisely, I suspect.
We should specify when is the latest time that an implementation can call
update-obsolete-instance, and then say that it is permitted to call it
any time earlier (after the class has been redefined). I now believe
that the latest time can simply be the time any of the four slot access
and modification functions (or internal equivalent) is called, in spite
of all the flaming against this we engaged in while discussing Danny's
proposal at the meeting last week.
The method on update-obsolete-instance defined on the class Object does
the following...
Instead, I would say the default method initializes only the newly added
slots.
Multiprocess systems are going to have to be careful about what happens
if other processes access the object before update-obsolete-instance
returns. I don't like the way Flavors does this now, which is: allocate
any new storage and evaluate any necessary initforms; with other
processes locked out, check again that the instance is still obsolete,
then convey the slots to the new storage and link things up, now with
processes running freely again, call the user's methods. Maybe the
user's methods should run with other processes locked out, especially if
newly added slots are only initialized by these methods. Of course CLOS
doesn't have to say anything about the extension to multiple processes.