[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
change-class and class redefinition
- To: Common-Lisp-Object-System@SAIL.STANFORD.EDU
- Subject: change-class and class redefinition
- From: David A. Moon <Moon@STONY-BROOK.SCRC.Symbolics.COM>
- Date: Mon, 19 Jan 87 23:56 EST
- References: <861020222040.5.MOON@EUPHRATES.SCRC.Symbolics.COM>, <861021103246.4.GREGOR@AVALON.isl.parc.xerox.com>, <861117153938.3.GREGOR@AVALON.isl.parc.xerox.com>, <861212100117.4.SKEENE@JUNCO.SCRC.Symbolics.COM>, <861217234009.5.MOON@EUPHRATES.SCRC.Symbolics.COM>, <861228213807.5.MOON@EUPHRATES.SCRC.Symbolics.COM>
Here's what I have so far on the subject, culled from the referenced
messages plus my own head. This is unfinished, but it seems worth
putting it out for comments in its current condition. This message
won't get out of the building until the snow stops, but I can hope
that it will get to SU-AI sometime Tuesday morning.
Class changing writeup
First we will discuss CHANGE-CLASS. What happens when DEFCLASS redefines
a class is discussed later.
Changes to the change-class writeup:
In the arguments field, it isn't really true that instance can be any
object. The arguments field should allude to the restriction discussed
in the remarks field.
Gregor's example of converting between rectangular and polar coordinates,
mailed out 17 Nov 86, could be inserted into the examples field.
All other fields are okay except the remarks field. Replace it according
to the following:
(1) Not all objects can change their class
If C1 and C2 are classes that were defined by DEFCLASS, without using the
:METACLASS option in either case, and X is an instance of class C1, X's
class can be changed to C2. This is the only case for which this standard
guarantees that CHANGE-CLASS is allowed. Both before and after the call
to CHANGE-CLASS, X's metaclass is the default metaclass.
Whether CHANGE-CLASS is allowed for instances of a different metaclass
than the default one is up to the implementor of the particular metaclass.
The Metaclass Protocol document will describe how to control this.
Implementations can choose to support CHANGE-CLASS in additional cases if
they desire. For example, this standard does not require CHANGE-CLASS to
or from a standard-type-class to be supported, however it is valid for an
implementation to support it for some standard-type-classes. In any case
where CHANGE-CLASS is not allowed, it signals an error.
(2) Changing the metaclass
Whether CHANGE-CLASS is allowed to change an object's metaclass is up to
the implementors of the two particular metaclasses. The Metaclass Protocol
document will describe how to control this.
(3) Preservation of slot values
CHANGE-CLASS preserves the values of slots that are common between the old
and new classes, and initializes any remaining slots of the new class.
Specifically, for each :ALLOCATION :INSTANCE slot of the new class, if
there is a slot by the same name in the old class (with any :ALLOCATION
other than :NONE), the slot's value is preserved. This means that if the
slot has a value, the value returned by SLOT-VALUE after the CHANGE-CLASS
is EQL to the value returned by SLOT-VALUE before the CHANGE-CLASS, while
if the slot is uninitialized, it remains uninitialized and no error is
signalled.
Each :ALLOCATION :INSTANCE slot of the new class with no slot by the same
name in the old class is initialized to the value of its :INITFORM, or
remains uninitialized if there is no :INITFORM. This is the same as the
initialization done by MAKE-INSTANCE, except that no initialization methods
are invoked and no MAKE-INSTANCE initialization arguments are present.
The value of each :ALLOCATION :INSTANCE slot of the old class with no slot
by the same name in the new class is discarded.
(4) CLASS-CHANGED methods
[CLASS-CHANGED was the best name I could think of off-hand. In Flavors
this is named TRANSFORM-INSTANCE. In CommonLoops it is named
CHANGE-CLASS-INTERNAL, I believe.]
After completing all other actions, CHANGE-CLASS invokes the generic
function CLASS-CHANGED on two arguments, named previous and current,
representing two views of the instance. The returned value is ignored.
The default method for CLASS-CHANGED does nothing, but programmers can
define their own methods to do such things as initialize new slots
differently from MAKE-INSTANCE or copy information from slots that only
exist in the old class into differently-named slots in the new class.
The first argument, previous, is an instance of the old class created to
hold the old slot values temporarily. It has dynamic extent, thus it is an
error to reference previous in any way after CLASS-CHANGED returns. The
typical use of previous is as an argument for SLOT-VALUE, WITH-SLOTS, or an
accessor generic function, to extract old slot values. In fact any function,
generic or not, can receive previous as an argument; thus information
about previous that is not directly stored in slots can be extracted.
The second argument, current, is the instance given as the first argument
to CHANGE-CLASS. At the time CLASS-CHANGED is called it has been fully
converted to the new class.
CLASS-CHANGED methods can, of course, make the slot value preservation
behavior of CHANGE-CLASS different from that described earlier.
(5) Restrictions on when CHANGE-CLASS can be called
[this section needs work]
There are two semantic difficulties associated with CHANGE-CLASS. The
first problem occurs if an object's class is changed while a generic
function is executing with that object as an argument. If the object's
class is used for method selection, the currently executing method might
now be the wrong method, not applicable to the object's new class. No
known \CLOS\ implementation can undo the effects of executing the wrong
method and cause the right method to be executed instead, so the wrong
method simply continues executing. When a particular generic function
invocation invokes multiple methods because of method combination or
CALL-NEXT-METHOD, an implementation is permitted, but not required, to
decide on the set of methods to be invoked at the beginning of the generic
function invocation. Thus the set of methods invoked after the class has
been changed is implementation-dependent and not defined by this standard.
The second problem concerns optimization of slot access. This standard
permits, but does not require, an implementation to compile WITH-SLOTS in
an early-binding style. [Does this apply to explicit calls to SLOT-VALUE
inside methods also?] This means that the determination of how slots are
to be accessed (their memory address and whether a method needs to be
invoked) can be done when the actual slot access occurs, when the
WITH-SLOTS is entered, when the method containing the WITH-SLOTS is
entered, or when the generic function that invokes the method is called.
This implementation freedom is a concession to efficiency, which is judged
more important than the semantics of CHANGE-CLASS. If the class is changed
after the determination of how slots are to be accessed and before the
actual slot access, the results are undefined. This means that if a method
changes the class of an argument, that method, and any other methods invoked
by the same generic function invocation, must not access slots of that
argument afterwards.
[Another way to explain this is to speak of an activation record corresponding
to the lexical context in which the class of an object is known (e.g. because
of a parameter specializer or because of a method discrimination), and
to say that changing the class of the object puts the activation record
into "a wierd state".]
Note that the call to CHANGE-CLASS that causes semantic difficulties might
not be lexically visible. CHANGE-CLASS could be called by a function that
is called by a method. All that matters is that the class is changed while
the method is dynamically active.
Note that in multi-process systems, the semantic difficulties discussed
above occur even when the generic function invocation is in one process and
CHANGE-CLASS is called in a different process.
[The following comes from Flavors. I believe all implementations should
be able to support this, but if some implementation has great difficulty
implementing this we could relax the standard.]
The second semantic difficulty occurs when CHANGE-CLASS changes the
arrangement in storage of the slots. By special dispensation, this
difficulty never occurs for the implicit call to CHANGE-CLASS that
occurs when a class is redefined. In addition, the programmer can
avoid this difficulty by obeying the following rules:
If the old and new classes have the same slots, and inherit them from the
same superclasses, then their arrangement in storage will be the same.
This can be accomplished by using mixin classes that do not define any
instance variables of their own, and using CHANGE-CLASS only to change
which of these mixins are included. A more complex solution ensures only
that the slots that are actually accessed remain in the same arrangement
in storage. These slots must be inherited from common superclasses. The
old and new classes can have additional slots supplied by superclasses
earlier in the precedence list than them. [This is not explained very
well, the idea is that slot allocation is from the least-specific-class
first, i.e. in reverse order of precedence, so if only slots in the common
tail of the class precedence list are accessed, it's guaranteed that those
slots won't have changed their arrangement in storage.]
===============
Next we discuss what happens when DEFCLASS redefines a class in a way
that changes the set of slots in an instance, or their order in storage.
Note that the affected class might not be the one whose DEFCLASS was
re-evaluated; it could be a subclass of that class.
[Part of the remarks field of DEFCLASS will be replaced by this.]
Redefining a class modifies the existing class object to reflect the new
class definition; it never creates a new class object for the class. All
methods applicable to the class remain, except for methods created by the
:accessor, :reader, :accessor-prefix, and :reader-prefix options when those
options have been changed in the DEFCLASS form.
Redefining a class in a way that changes the set of slots in an instance,
or their order in storage, also creates an [italics] obsolete class object.
This class has no name. The only instances of this class that can ever
exist are those created as the [italics] previous argument to a CLASS-CHANGED
method. All methods applicable to the class before redefinition, including
methods created by the :accessor, :reader, :accessor-prefix, and
:reader-prefix options, become applicable to the obsolete class also. This
allows a CLASS-CHANGED method to extract information from its [italics]
previous argument. [Are future changes in methods reflected to the
obsolete class? Deal with additions, deletions, and redefinitions
separately? Should the standard define this?]
When an instance of a class that has been redefined is accessed, the
instance is automatically updated to the new class definition. "Accessed"
means calling a generic function with the instance as an argument that is
used for method selection, calling one of the functions CLASS-OF,
SLOT-VALUE, TYPE-OF, TYPEP [maybe others?]. Updating an instance does
not change its identity as defined by the EQ function; it changes the class
and slots of that particular instance; it does not create a new instance.
Whether updating an instance consumes storage is implementation-dependent.
Updating an instance calls CHANGE-CLASS, whose behavior and programmability
using CLASS-CHANGED methods was described earlier.
[Still to be defined: how if you redefine a class several times, you can
create CLASS-CHANGED methods that perform updates from one particular
version of the class to another particular version. The simplest
answer is that you can't, instead you have to write your methods to
work for any version of the class.]
[What about addition/removal of class variables?]