[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Redefining Classes



Summary: When redefining a class causes instances to be updated can be
implementation-dependent.  (update-from-obsolete-instance instance
added-slot-names removed-slot-names removed-bound-slot-values); the
primary method uses initforms to initialize the slots whose names are
listed in added-slot-names.  update-from-obsolete-instance is called no
later than the first slot read or write after the class is redefined.
The retained slot values are already in place when
update-from-obsolete-instance is called.  Those are my opinions.

    Date: 28 Sep 87 14:51 PDT
    From: Danny Bobrow <Bobrow.pa@Xerox.COM>

	    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.

    I agree with you on the choice of how one specifies this.  In the
    metaobject protocol, we currently have a generic function

    all-slot-names class &optional allocation 

    which returns a list of slot names.

    The allocation argument can be an allocation symbol (e.g. :instance,
    :class), or nil meaning names for slots regardless of allocation.  For
    allocation = :instance, the list returned by the implementation should
    be one that is ordered by the implementation.  Thee is no method to
    specify the order of instance slots in the instance.  An implementation
    is free to decide on the layout (e.g. so that reversing two slot names
    in a defclass form does not necessarily obsolete the instances).  By
    documenting all-slot-names, we can avoid the specification of the
    ordering rules, and still provide a specification of what we mean.

So what you're saying is that alternatives 1 and 2 are really the same.
We can say that defclass calls make-instances-obsolete precisely when
the value of (class-all-slot-names class ':instance) is not EQUAL to its
former value, but then we can obscure that by not specifying precisely
what class-all-slot-names does.  OK, let's not try to define precisely
when defclass calls make-instances-obsolete.

	I have two problems with [update-obsolete-instance].
	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.
    We could call it update-from-obsolete-instance, or
    conform-instance-to-class.  Since it takes ans instance as an argument,
    I don't like names like class-redefined -- which seems to focus on the
    class.

Good point.  update-from-obsolete-instance appeals to me, although it
might be thought to imply that two instances are involved.

	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.
  
    We could designate such a value (make it be the value of a globally
    accessible variable).  Then putting such a value in  a slot would be
    equivalent to making it unbound.  I can hear the ARGHHH from here, but
    it does make accessible something that we are having to make
    programmable (e.g. SLOT-MAKUNBOUND ...).  Aside from matters of taste
    (and I could be convinced on this fairly easily), why is it you could
    not implement this -- is there a particular tradeoff that you are not
    being explicit about?

It doesn't work to have a magic Lisp object that means "unbound" when it's
the value of a slot, because then you can never talk about that value in
any way that involves putting it into a slot.  If you think that's a matter
of taste, the other answer is that my hardware has the unbound slot value
built into it, and it would cost me a couple million dollars to change it.
Here we have both the rarified philosophical argument and the businesslike
pragmatic argument.

	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."
    I feel that making unbound slots be initialized is a feature, not a bug.
    One good reason to make a class obsolete may be to cause formerly
    unbound slots to be initialized.  That is, one could redefine a class,
    adding an initform option, and be sure that any instances that had that
    slot unbound would now have it bound to the specified value.

To my way of thinking it is philosophically inconsistent to have different
rules for whether a slot's value is changed when the class is redefined,
depending on slot-boundp.  At this level, "unbound" should be treated like
just another value.  I hope you don't think this is too inconsistent with
what I said just above: "unbound" is a slot value, but it is not a Lisp
object.

I think it's better to do things like filling in previously unbound
slots in old instances with update-from-obsolete-instance (or whatever
name we choose) methods.

	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.
    Can such linked values only be used inside of objects, and not within
    lists?  I though inivisible pointers can be used anywhere.  I don't
    understand what the assumptions underlying this extension are.

They can be used in lists, but the point is that care needs to be exercised
when moving them around.  The net effect is that the code that moves slot
values from one piece of memory to another, when an instance has to have
new memory allocated for it, can't be written portably in our system.  But I 
don't see why anyone would want a portable version of it.

	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.

    Without the reasons given above, I still like this proposal as well as
    the one I wrote up.  It supports the proposed model at a better level. 
	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 actually think this is fine.  But I would also say that the underlying
    implemention also initializes existing slots that were unbound, using
    the initforms in the class, if any.
   
I still think that is philosophically inconsistent.

	I would say the default method initializes only the
	newly added slots.

    Fine I will write it up this way.

Thanks.

	    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.

    Agreed.

I'd like to hear Patrick's opinion on this timing issue before I have much
confidence that we have figured it out.