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

Mopping up after the fact



[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 --