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

Re: [spr7983] MOP programming - compile vs. load



[This matter has been assigned the tracking identifier "spr7983".
Please refer to it in any followup communication.  Also, be sure
to cc bugs@franz.com so that if I am unavailable someone else will
be able to respond.]

   From: bha <@ada3.ca.boeing.com:bha@gumby.boeing.com>

   ACL 4.2beta

   I am attempting to implement an extension to CLOS via the MOP and am
   having some trouble with compile versus load time execution of class
   metaobject methods.

   As background, here is a brief explanation of what I am trying to
   accomplish.  I have a need to "surround" *every* method of an object
   with some body of code.  It is highly desirable that the
   implementation mechanism is transparent as possible with respect to
   those methods that are surrounded.  I don't believe that defining a
   special method metaobject is appropriate for implementing surround
   behavior.  Anyway, not all methods of a particular generic function
   necessarily have the surround behavior.  I also do not think that
   :around methods are the appropriate mechanism for implementing
   surround behavior.  The surround behavior is more closely associated
   with the behavior of a particular class, not the individual methods.
   For class hierarchies, a "mixin" class may be a component of a class
   that does not have the surround behavior.  Thus, in this context the
   methods associated with the mixin class should not have surround
   behavior.  Therefore, I believe that defining a class metaobject is
   probably the most appropriate mechanism for implementing surround
   behavior.  The class metaobject can then be used to "declare" the
   intention that a particular class (and therefore its methods) have
   surround behavior.

Hmm, the implementation you describe below suggest you may be
misspeaking slightly.  What you seem to want is to have surround
behavior on the entire effective method for generic-functions defined
on one of your classes, not separately on each applicable method
called by the effective method.  This is a quite different thing.

   ...

   When an instance of an encapsulated class is made, its dual (the
   encapsulator) is also made.  What is returned from the make-instance
   method for an encapsulated class is actually the encapsulator
   instance.  Thus, any methods destined for the encapsulated object are
   actually handled by the encapsulator object.  Any "surround"
   processing is performed by the encapsulator trampoline method, then
   control is passed to the real method in the encapsulated object.  For
   gated objects, this involves obtaining a "lock" associated with the
   encapsulator object before calling the real method in the encapsulated
   object.

   OK, all this stuff seems to be working except for one "tiny" problem.
   When an encapsulated class is "compiled," it is actually instantiated
   (that is the encapsulated-class class metaobject is instantiated).
   This causes the initialize-instance method on the encapsulated-class
   class metaobject to be called.  This is where I hook in to define the
   encapsulated class.  The problem is, at *compile time*, certain data
   structures (such as the superclass of an encapsulated class) are ill
   defined (the superclass is a forward-referenced-class).  When the
   "compiled" code is "loaded", this causes the class metaobject to be
   seemingly instantiated again (?) and initialize-instance to be called
   again (no, *not* reinitialize-instance).  But my processing (which the
   first time thru at compile time did a defclass of the encapsulator
   class) now ends up (indirectly) invoking reinitialize-instance on the
   encapsulator class (since I do the defclass of the encapsulator class
   again as I am unable to distinguish compile time from load time
   execution).  What I need to do (I think) is to defer my processing
   that is hooked into the initialize-instance method on the
   encapsulated-class class metaobject until *not* compile time.  Note
   that when I *evaluate* the defclass form of the encapsulated class
   (instead of compile it), everything works just fine!  Also, what is
   going on at compile and load time such that the initialize-instance
   method is called two times - once at compile time then again at load
   time?

The crux of this behavior is the problem faced by compile-file, and
the requirements placed on compile-file processing of DEFCLASS by
CLtL2 and the dpANS.  Basically, compile-file processing of a DEFCLASS
at compile-file time _shouldn't_ define the class in the compiling
lisp world because that could break things in the running world if a
like-named class exists but is incompatible.  This is no different
from the behavior of DEFMETHOD in not putting the method into effect
at compile time, but only later when the compiled file is loaded.

On the other hand, DEFCLASS is obviously required to `define' the
class because there are lots of ways the class object is needed at
compile-file time.  So the solution is for the DEFCLASS macroexpansion
to create a new class object at compile time and secret it away in he
environment the compiler maintatins to contain information about the
ongoing compilation.  This is no different from the behavior with
DFSTRUCT, DEFTYPE, DEFMACRO, etc.

So, you can't assume that the class object being manipulated at
compile-file time is the same EQ object that would be operated upon if
that compiled file is subsequently loaded into the same running lisp
image.  It isn't.

To focus on your original problem, I don't have a lot of time to try
to write a solution, which is a shame, because the problem is
interesting.  Here's just a casual comment:

One thing that wasn't clear in the description of your solution was
how CLOS or DEFMETHOD knows to apply the trampoline mechanism to a
particular generic function.  My guess you DEFGENERIC these functions
to have a different metaclass?  If so, then I wonder why you use the
rather involved complicated encapsulator class mechanism instead of
using a method on CLOS:COMPUTE-EFFECTIVE-METHOD to do the wrapping.
Since COMPUTE-EFFECTIVE-METHOD receives a list of applicable methods,
locking behavior could if necessary depend on the presence of a method
with some particular qualifer which would be deleted before your
COMPUTE-EFFECTIVE-METHOD method calls it's next method.  (This is
similar to defining your own method combination, but there is no way I
know of for a method combination to do wrapping and delegate the rest
of method combination to some other method combination, and so you'd
have to duplicate all of standard method combination yourself, which
is unattractive.)

What I mean is something like:

(defclass my-generic-function (standard-generic-function) ()
  (:metaclass clos:funcallable-standard-class))

(defmethod clos:compute-effective-method ((gf my-generic-function)
					  method-combination
					  methods)
  (flet ((locking-qualifier-p (meth)
           (equal (clos:method-qualifiers meth) '(:locking))))
    (if (some #'locking-qualifier-p methods)
      (let ((values (multiple-value-list
                      (call-next-method gf
                                        method-combination
                                        (remove-if #'locking-qualifier-p
                                                   meths)))))
	(apply #'values `(with-locking ,(car values)) (cdr values)))
      (call-next-method))))

Locking behavior depends on there being a method defined like this,
but the method is never actually called:

(defmethod some-gf :locking ((x one-of-my-classes)))

I haven't tried running this, but I think it is portable use of the
MOP.