[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Object creation discussion (at last!)
I can see from the misunderstanding in the mail that I did an abysmal job
of communicating my ideas. I'll try to send a more comprehensible essay
tomorrow, but for now I would like to just clarify a couple of points and
then make some comments on excerpts from the mail in places where they
might help communication.
Complexity and simplicity are partly matters of taste, and largely matters
of point of view. I try to take the point of view of the majority of
programmers, who are using the outer interfaces of the system without
necessarily understanding everything that's going on inside. No matter how
beautifully we explain it, most programmers simply won't read beyond the
point where they think they know everything they need to know to get their
immediate job done. Now, it may be that we will decide that we would
rather make life more complicated for people writing initialization methods
in order to make the conceptual explanation shorter. That would be okay
with me if it's done for good reasons, but we shouldn't just dismiss the
other way without understanding it and understanding why I proposed it. I
have a feeling this message isn't going to explain it adequately either,
and if that happens I will apologize and follow up with a more carefully
written explanation.
One key point that I was trying to convey, and I think partially succeeded,
was the need for information hiding (abstraction) in the arguments to
make-instance and in relations among the various initialization
specifications attached to a class and its superclasses.
Another key point, which I don't think came through, was that each initarg
should be explicitly defined, and should be defined in exactly one place.
This is simply good modularity.
Some aspects of what I proposed were reacting to user complaints about the
way Flavors does it. I have the ambition of making this new standard
better than Flavors.
All this leads to the idea that initargs of types 2 and 4 (in the
nomenclature of my 16 Apr message) should be defined by methods, since
their meaning is implemented by methods. Similarly, type 3 should be
defined by slot-options, since their meaning is entirely involved with
slots. In Flavors, type 4 have the ugly property that they have to be
defined in two places, once in the method that implements them and again in
a defflavor form; it's easy to get these two places out of sync. Hence the
proposal to use the lambda-list of a defmethod as the way to define these
initargs. Along with this, I tried to eliminate clunky syntax from
initialization methods, hence the elimination of &allow-other-keys and the
elimination of having to write :before all the time. If this is too
confusing, we could invent a new syntax that both defines initargs and
defines the method that implements them. Clearly this is the weakest part
of my proposal.
We could obviously simplify things a lot by getting rid of the :constructor
option to defclass. I didn't put it in, at least not this year. I do think
it ought to remain, in spite of its inherent complexity in any initialization
proposal (previous ones I've seen have glossed over this rather than solving
it), because I believe many users will find it quite useful.
Date: Wed, 22 Apr 87 13:42 PDT
From: Gregor.pa@Xerox.COM
The special method combination type for initialize instance adds
considerable conceptual overhead for very little functional gain.
The goal was not functional gain, that is, the ability to program something
you couldn't program before, but rather syntactic simplicity. Perhaps in
the end we'll decide it's not worth it, but I'd like us to keep considering
the question a bit longer.
I admit that the basic method combination rule is simple enough to
understand. But there are other aspects of this method combination
types which will cause people to ask themeselves questions they will
have a hard time answering with any kind of simple model of this thing.
- if I don't have to say &allow-other-keys here, why do I have to
say it in the normal kind of method combination?
True, this is a problem.
- why is it that I can't count on the methods getting all the initargs
if I use &rest in the lambda-list? Note that this also robs useful
functionality which exists everywhere else in Common Lisp. Specifically,
it makes the &rest args &allow-other-keys idiom not work. This idiom
is very useful for methods which want to process all the initargs in
the init-plist in ways that makes those initargs interact.
I can't figure out precisely all you're saying here, but I think this just
stems from a half-baked idea that I wouldn't have included in the proposal
if I had spent more time thinking about it before sending it. I was trying
to do an efficiency optimization involving not consing lists of slot-filling
initargs that have been defaulted, and I now think that was premature. We
can revisit the question after the major framework is agreed upon, plus I
always (except when I fall from grace) believe in the principle that designs
should first consider what is right, and optimizations should be subordinate
to that, fitting into an existing framework rather than distorting it.
- If I redefine a initialize-instance method, will that effect the
interpretation of all the :constructor lambda-lists involved? Will
all those :constructors get fixed?
Of course the constructors have to get fixed if you change something in a
way that changes the compiled code that was supposed to have been generated
for them. I think that fact is independent of details of all object
creation proposals. The specific example that I think you were thinking
about is when a positional parameter of a constructor is changed from
something that only fills a slot and isn't cared about by initialize
methods, to something that both fills a slot and is seen by initialize
methods. In my proposal, adding an initialize-instance method could do
this. In your most recent one, evaluating a defclass could do this.
*** the complex rules for interpreting the lambda-list of :constructors
.... example elided ....
I don't see any problem here that I added. The lambda-list syntax of
constructors is like defstruct, not like defun, but that's already in
87-002.
Your example also touches upon the lambda-list of initialize-instance
methods. Here the default for an argument that is already defaulted by
defclass options would never be used, because all calls that reached the
initialize-instance method would necessarily have to supply that argument.
But I don't think there's anything new to Common Lisp in that.
- what arguments does allocate-instance get?
allocate-instance was intended to work exactly like initialize-instance,
however the latter turns out.
Date: Wed, 22 Apr 87 17:28 PDT
From: Gregor.pa@Xerox.COM
The default-initargs defclass option serves two purposes. It specifies
all the initargs which can be passed to make-instance of this class, and
it specifies default values for some of those initargs.
This is an important modularity mistake, in my opinion. By combining these
two things, which ought to be separate, you have lost the ability for one
class to specify a default value for an initarg defined by another class.
If specifying a default value always defines an initarg, there is no way to
check the consistency of the set of initargs with default values specified
against the set of initargs actually defined. If there is a misspelling,
the default value will simply be discarded, since no method will receive it
and do something with it.
constructors can be thought of as providing a "boa" syntax for specific
calls to make-instance.
This can't work as long as constructors are allowed to fill slots that
make-instance is not allowed to fill, which I think is an important ability
because it gives the programmer more control over interfaces.
Date: Thu, 23 Apr 87 08:03:20 pst
From: Jim Kempf <kempf%hplabsc@hplabs.HP.COM>
From subsequent discussion, it seems as if an additional DEFCLASS
option, :DEFAULT-INITARGS is being proposed. I wonder if this
is needed, considering that a user can already specify an initialization
value via the :INITFORM option? Perhaps the metaclass ought to
attend to this? Or the :INITFORM and :DEFAULT-INITARGS options should
be merged?
This indicates that I completely failed to convey what :default-initargs is
about, and that in turn may possibly indicate that :default-initargs should
be flushed. In my proposal there are two ways to specify a default for an
initarg: at the point of definition, and remotely. To explain: when
defining an initarg one can specify right there, locally, a default value
form. For initargs that fill slots, this is the existing :initform
slot-option; no need to invent anything new there. For initargs that are
implemented by methods, this is the existing defaulting mechanism in the
lambda-list; again, no need to invent anything new. Now for remote
defaulting: here the idea is that one class defines what an initarg means,
while a second class specifies a default for it; this is :default-initargs.
When you mix the two classes together, the default meets up with the
implementation and things work. The reason remote defaulting is desirable
is twofold: (1) it's often useful when mixing classes together for one
class to customize another class by specifying initialization options, in
addition to the existing ability to customize a class by shadowing or
wrapping methods; (2) for modularity reasons, when class A customizes class
B by specifying an initarg that class B defines, class A shouldn't have to
know whether the initarg fills a slot or is implemented by a method. If
not for reason (2), remote defaulting wouldn't require a new mechanism,
because it could be implemented using the existing mechanism of :initform
for slot initargs plus call-next-method-with-arguments (proposed but not
yet accepted) for method initargs. But I think reason (2) is important.
I don't understand. What is understood here by the word "module"?
Has it any relation to *MODULES* on pg. 188 and thus to the
PROVIDE/REQUIRE mechanism?
No relation. I meant the general computer-science concept of modules as
subdivisions of a program that have some independence from each other, not
something realized as an object in an implementation.
What if a user defines a constructor function to make an object out
of a list, or to return a symbol? If the constructor function tries
to call initializaton methods, then what will be the result? Also,
if the initialization succeeds, method definition and lookup might
do the wrong thing if the low level structure of an instance is
not what is expected. As an example, say the constructor function
returns an integer, and there are two methods on a generic function,
one with an INTEGER selector and one with the same class as the
erring constructor function.
In addition, I think the IN-LINE declaration could help getting speed
out of a constructor.
By "constructor", I meant the functions defined by the :constructor option
to defclass. The syntax of that option doesn't admit any of the above
possibilities.
I wonder if you were getting at a different issue: what if the value
returned by the allocate-instance method is not an instance of the exact
class being instantiated? If the caller of allocate-instance simply
assumes the type of this value is correct, all kinds of terrible things
could happen, especially if slot-filling is open-coded in constructors.
Danny's chapter 3 speaks of "recognizable blocks of storage", and I think
we need to say something like that here. I think this issue is largely
independent of the other details of object creation proposals, and needs to
be resolved on its own. It's hard for me to say much about it since I don't
understand why anyone would need to customize allocate-instance.
Are the initforms run in the context of INITIALIZE-INSTANCE?
We agreed some time ago that they are in the lexical environment in which
the defclass was evaluated.
Is WITH-SLOTS acceptable within an initform?
WITH-SLOTS works everywhere (although it works more efficiently in some
places). However, the object being created is not lexically available in
the environment of initforms, so it isn't possible to access its slots.
Some people have proposed that the object and the initargs be lexically
available to initforms, and this does open some intriguing expressive
possibilities. However, I think I prefer to say that anything this
complicated should be done in initialize-instance methods instead.
Date: 23 Apr 87 18:24 PDT
From: Gregor.pa@Xerox.COM
What is the purpose of :initform anymore? You only need it when you
want to specify the default value for a slot that isn't set by any
initarg. This means that we have two mechanisms which do almost the
same thing -- initargs which set slots are almost a superset of
initforms. This is a sure sign of bad modularity.
On the contrary, I think it's a sign of good modularity! The two
mechanisms do almost the same thing inside the implementation, but from the
point of view of someone on the outside who doesn't know the information
that is supposed to be hidden from him by modular abstraction, they do
rather different things. I could give a longer explanation of what I mean,
but it's late, this message is already too long to ask anyone to read
carefully, and I think I would just be repeating what I said a bit earlier.
(defclass position ()
(x
y)
(:default-initargs (nil 0 x)
(nil 0 y)))
What I am trying to do is seperate what I perceive as the separate parts
of what is going on into separate places.
The reason I don't like this is that the information about a slot is no
longer all in one place. Another way of saying it is that I think that
information in defclass should be organized spatially rather than
temporally, i.e. information pertaining to one slot should be together,
rather than putting information pertaining to one phase of object creation
together. One of the ways that I think defclass is better than defflavor
is that it has done a better job of spatial organization by putting more
of the information about a slot into slot options instead of scattering it
around in various options some of which refer back to the slot by name.