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

Object creation

I've been studying the huge piles of mail on this subject.  I'm not sure
that all of the ideas that were intended to be conveyed got through to
me, since there was so much mail and so many contradictions and minor
errors.  Hence I apologize if what follows misses the point.

I couldn't find anything that directly addressed the issues I raised in
my message of 29 May; the one dividing things up into eight parts, of
which only three are actually needed if we want to keep things simple. 
Did it get lost in the network?

My hardcopy of Dick's message of 31 May has "agreed" written all over
it.  The conclusion I drew was that we aren't doing automatic
programming here and the generic-function-dispatch mechanism is pretty
limited in what programming tasks it can do automatically for us. 
Still, it would be nice if we could come up with a general paradigm for the
procedural description of inheritance and use it uniformly for classes,
methods, slots, and initializations, and also make it available to users
with their own things-that-inherit; I don't have any specific
suggestions on this yet.  One thing that I think we have learned is that
it seems to be a mistake to try to abstract away the caching that
happens in every real implementation of inheritance; we need to separate
the generic functions that gather and combine information from the
superclasses, from the generic functions that are called when an object
is created; the cache that sits between them needs to be made explicit.

I do have some thoughts to offer on the various schemes that have been
discussed for object creation at the meta-object level.  It seems that
there are three rather different things that this could be for:
(1) The language used by ordinary, rank-and-file programmers to write
their programs.
(2) A procedural explanation of the semantics, so that there is no
magic and everything is explained in terms of something else.  This
seems to be where Patrick's suggested generic, high-level tools fit in.
(3) A way to modify very basic aspects of the system's behavior,
for instance changing the way initargs inherit.

Taking these in order:

(1) I feel that a successful standard will provide simple ways to do
simple things.  A good argument that controlling object creation only at
the meta-object level is too difficult is contained in Gregor's message
of 15 June.  In the x-y-rho-theta example, the compute-initargs method
for position looks impressive, but if you study it carefully you will
realize that it is actually a complicated no-op!  Regardless of whether
this method or the default method is called, the result will be the
same:  a list containing the supplied initargs plus all of the
unsupplied ones with their default values from class-default-initargs. 
I suspect that what happened here is that Gregor got so bogged down in
the complexity of describing the x/y/rho/theta class in these very
low-level procedural terms, that he forgot what the original point was. 
Gregor is a smart guy, so I think what this shows is that an
object-oriented system that works this way is too difficult for anyone
to use without making mistakes.

Actually, this is probably a non-issue.  Correct me if I am wrong, but
I think we have already agreed that we want a simple, high-level way to
do simple object-creation, with the procedural level behind the scenes
where it belongs.

(2) I think having a procedural explanation of the semantics is a good
idea.  If I earlier gave the impression that I was opposed to that, it
was a misimpression.  I do have some reservations, which I think fall
into two categories:

(a) This level of the standard involves a lot more detail than the
simple programmer interface, and there is correspondingly more
possibility for errors.  Two examples that I noticed were failure to
carry along a lexical environment with forms, so that they can be
evaluated in the lexical environment in which they were written; and
confusion between method selection by the class of the instance versus
by the class of the class.  These are small errors and fairly easy to
fix.  For example, the first could be fixed by using functions instead
of forms and the second could be fixed by being consistent.  The point
is simply that there are more things to go wrong and it will take longer
to get them right, because we are standardizing an implementation (at
some level of abstraction) rather than standardizing only a description
of how any implementation must behave.

(b) There is the danger of ripping too many holes in the veil of
abstraction between the user and the implementation, eliminating the
freedom to choose an implementation optimized for particular
circumstances.  We have often made this mistake in the Lisp Machine
world in the past, and paid for it later.  One example of this problem
is that I haven't been able to figure out whether some of the proposals
that have been discussed require the methods for class-default-initargs
to be called every time make-instance is called, thus ruling out
precomputation and caching of this information.  Similarly I wasn't able
to figure out whether initializing many of the slots by copying a
template containing the values of constant initforms would be a valid
implementation, or whether the initforms actually have to be evaluated
at the time we say they are evaluated.  A more serious problem is that
if the two operations, collection of the inherited default value forms
and evaluation of these forms, are combined into one generic function,
such as compute-initargs, it becomes impossible to collect these forms
without evaluating them and compile them into a function.  This is
really the same point I made earlier, that trying to hide the existence
of caches isn't working.  A third problem, which I mention because it's
of a completely different nature, is that if the standard mandates that
each little aspect of a class must be expressed as a method, and we
actually have to create method objects, compiled-function objects, and
generic-function dispatch table entries for each of those methods, it's
going to tie up a large amount of space.  In a large system like Genera,
a rough, no doubt inaccurate estimate I made came out to 3/4 megabyte.

The point of these reservations is that if we're going to include this
level of detail in the standard, we have to be very careful what we say
and be sure we know what it implies.

(3) I'm not very happy about the idea of the user being able to redefine
very basic aspects of the behavior of the system, such as the way the
supplied initargs are combined with the default initargs.  It seems like
this would weaken the idea of a standard, because there would be no
contract that the system could safely be assumed to obey.  Actually what
bothers me here is not the idea that it is possible to redefine these
things, because I'm an open-system kind of guy and if people want to yank
the foundations out from under themselves that's okay with me.  What really
bothers me is the idea that redefining this stuff will be part of ordinary
day to day programming, rather than just something that you do when you're
experimenting with new kinds of object systems; changing the rules should
be intentional, not accidental.

Where do we go from here?  I think we should go back to figuring out
what the language used daily by rank-and-file programmers is.  Once we
think we agree on that, we should verify that it makes sense by agreeing
on the procedural description of it; there will be some iteration of
these two steps (there has already been some).  The biggest issue seems
to be what suite of features we want in that language, for example, is
the extra complexity of allowing initargs to be defaulted worthwhile?