[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Mopping up after the fact
- To: mop@arisia.Xerox.COM
- Subject: Mopping up after the fact
- From: Jon L White <jonl@lucid.com>
- Date: Thu, 15 Nov 1990 23:01:06 PST
[Hmmm, "cleanup" issues for the Meta Object Protocol must be what we
mean by "mopping up"]
There's one point of development in the emerging MOP that is very
confusing, and needlessly so. Since the AMOP also follows this line
of design, it is sort of necessary to bring it up now before a confusing
paradigm is cast in concrete. There is surely time to correct it now.
Let me illustrate it with reference to the :DEFAULT-INITARGS option to
DEFCLASS. Consider:
(defclass foo ()
((slot1 :initarg :slot1)
(slot2 :initarg :slot2)
(slot3 :initarg :slot3 :initform 0 :type (and number (not syzygy))))
(:default-initargs :slot1 1 :slot2 2))
(defclass bar (foo)
((slot3 :initarg hue :type (or symbol integer)))
(:default-initargs :slot1 'one :slot3 (call-three)))
Question: what are the default initarg definitions for class BAR?
It might suffice to characterize them with the list:
((:SLOT1 . <function-to-return ONE>)
(:SLOT3 . <function-to-run (call-three)>)
(:SLOT2 . <function-to-return 2>))
or maybe even like:
(:SLOT1 <function-to-return 'ONE>
:SLOT3 <function-to-run (call-three)>
:SLOT2 <function-to-return 2>)
the point being that each such definition is a resolution of the
possibly many direct specifications found in the given class and
in its superclassess. (In this case the resolution doesn't involve
any compound compositions -- just selection based on precedence;
but it could have been otherwise).
Yet it has been suggested that the alist format of the definition
isn't satisfactory since one might conceivably want to write
metaobject protocols that treat different categories of initial
value fetchings differently, *** and even want to attach methods
at certain points in their construction or application ***. [The
proplist format would be even worse.]
Thus the format for a single initializer:
(:SLOT1 . <function-to-return ONE>)
or:
(:SLOT3 . <function-to-run-code (call-three))
is a poor choice since its class is CONS and that would just merge in
with all those other silly cons cells and be of no benefit for writing
type-specific methods. It would be better instead to use:
#<Initializer-constant ONE 123456)
#<Initializer-executable ... 123457)
because then it would be possible to attach methods that activate
differently depending on whether or not the initializer is for a
constant value. [Really. This is no joke. I know implementations
for which this kind of optimization is really done.]
So; I am not seriously suggesting any such conversion for the format
of the proposed MOP facility CLASS-DEFAULT-INITARGS. I just want to
point out now that even if we were to opt for such a format change,
there would be no reason whatsoever to recode the storage format of
the items accessed with CLASS-DIRECT-DEFAULT-INITARGS. The contents
of CLASS-DIRECT-DEFAULT-INITARGS currently mirrors almost exactly the
source-code specifications handed in through ENSURE-CLASS (or whatever),
and there would be no need of any kind to prematurely convert these
specifications into a set of partial initializer objects.
*** Only the one function *** -- COMPUTE-CLASS-DEFAULT-INITARGS -- ever
looks at the "direct" default initarg specifications; and in some cases
it would be premature to classify them into Initializer classes until
FINALIZE-INHERITANCE is called on that class. The natural course is to
leave the "specifications" in their list-structure original format, and
to let the function invoked at inheritance finalization time be the sole
one responsible for collecting the pieces of spec into a full, effective
"definition."
That being said, shall we draw the parallel to the slot specifications
that are handed to ENSURE-CLASS, and the SLOT-DEFINITION objects that
each class contains somehow as a way of describing the properties of
the slots of its instances.
First off, the inheritance step of collecting the little particles of
specification into one coherent definition is certainly more complex.
Let us go back to the above example again:
(defclass foo ()
((slot1 :initarg :slot1)
(slot2 :initarg :slot2)
(slot3 :initarg :slot3 :initform 0 :type (and number (not syzygy))))
(:default-initargs :slot1 1 :slot2 2))
(defclass bar (foo)
((slot3 :initarg hue :type (or symbol integer)))
(:default-initargs :slot1 'one :slot3 (call-three)))
Is the item "(slot3 :initarg hue :type (or symbol integer))" a
definition for the kind of slot that exists in class BAR? No. It is
only a partial specification -- it doesn't even mention the initform
that will be part of the definition; and the :type component is only a
little piece of the compound TYPE slot that will exist for the final,
effective definition.
How about "(slot3 :initarg :slot3 :initform 0 :type number)" --
is it a definition of anything real? No. Again, it is just one
of many "specifications" that are mixed and resolved into a final
effective definition.
So how are these partial, or "direct" specifications used? are
they used in in a context such that the type of the object holding
that information could profitably be specialized upon? No. Just
as in the case of the :DEFAULT-INITARGS specifications, *** only the
one function *** -- COMPUTE-SLOTS -- ever looks at the "direct" slot
specifications, and in some cases it might be premature to classify
them into final SLOT-DEFINITION sub-classes until FINALIZE-INHERITANCE
is called on that class. The natural course is to leave the
"specifications" in their list-structure original format, and to let
the function invoked at inheritance finalization time be the sole one
responsible for collecting the pieces of spec into a full, effective
"definition."
Finally, one might argue that there are some components accessible in
both "specifications" and "definitions" that are similarly named, and
thus one can economize on function names by overloading the accessors.
For example, there is a TYPE component on both. So by merging the
underlying representations, you can use just one function named:
SLOT-DEFINITION-TYPE
to access the component; otherwise you might need two function names:
SLOT-SPECIFICATION-TYPE
SLOT-DEFINITION-TYPE
But it is precisely this kind of merger into a single data type of the
two fundamentally different semantic meanings of "specification" and
"definition/descriptor" that makes the PCL code so intractably hard to
read. The code's purposes would have been much clearer had the two-name
approach been used above rather than the superfluous overloading of
SLOT-DEFINITION-TYPE. Yes, even if the two names both pointed to one
single generic function, the code would have been much clearer to read!
So why not forego the packing and unpacking of the slot specification
information. Let's have just one SLOT-DEFINITION meaning.
-- JonL --