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