These notes correspond to the "March 92 PCL (beta1)" version of PCL. This version of PCL is much closer than previous versions of PCL to the metaobject protocol specified in "The Art of the Metaobject Protocol", chapters 5 and 6, by Gregor Kiczales, Jim des Riveres, and Daniel G. Bobrow. [Please read the file may-day-notes.text also. Most of that file still applies.] Support for structures You can use structure-class as a metaclass to create new classes. Classes created this way create and evaluate defstruct forms which have generated symbols for the structure accessors and constructor. The generated symbols are used by the primary slot-value-using-class methods and by the primary allocate-instance method for structures. Defmethod optimizes usages of slot-value (when no user-defined slot-value-using-class methods exist) into direct calls of the generated symbol accessor, which the compiler can then optimize further. Even when there are user-defined methods on slot-value-using-class, PCL does a variety of optimizations. If your implementation's version of the *-low.lisp file contains definitions of certain structure functions (see the end of low.lisp, cmu-low.lisp, lucid-low.lisp, and kcl-low.lisp), then structure classes are supported for all defstructs. In this case, structure classes are created automatically, when necessary. New Classes: structure-class structure-object slot-class slot-object structure-direct-slot-definition structure-effective-slot-definition Improvements to slot-access Optimization for slot-value outsize of defmethod Optimization for slot-value inside of defmethod, but not of a specialized parameter. Optimizations that work even when there are :around methods on slot-value-using-class. New types: `(class ,class-object) `(class-eq ,class-object) New specializer class: class-eq-specializer Every class has a class-eq specializer which represents all the direct instances of that class. This is useful in *subtypep. For example, here is the way generic-function initialization checks that the method-class is valid: (and (classp method-class) (*subtypep (class-eq-specializer method-class) (find-class 'standard-method))) If you want to define methods having class-eq specializers, see "Initialization of method metaobjects". The default behavior of PCL is to disallow this. compute-applicable-methods-using-types caching improvements no magic-generic-functions list This simplifies some things, but complicates some other things. I wanted to support user-defined classes which are their own metaclass. You can now do: (defclass y (standard-class) ()) (defmethod validate-superclass ((c y) (sc standard-class)) t) (defclass y (standard-class) () (:metaclass y)) method-function changes (see the comments for make-method-lambda, below) final dfuns ------------------------- gfs which obey AMOP ch 6 add-dependent add-direct-method add-direct-subclass add-method allocate-instance compute-class-precedence-list compute-default-initargs compute-effective-slot-definition [Note: compute-effective-slot-definition relys on compute-effective-slot-definition-initargs and effective-slot-definition-class. compute-effective-slot-definition-initargs is quite useful, but is not in AMOP ch 6.] compute-slots direct-slot-definition-class effective-slot-definition-class ensure-class ensure-class-using-class ensure-generic-function ensure-generic-function-using-class eql-specializer-object extract-lambda-list extract-specializer-names finalize-inheritance find-method-combination funcallable-standard-instance-access {generic-function-method-class, generic-function-method-combination, generic-function-lambda-list, generic-function-methods, generic-function-name} intern-eql-specializer make-instance map-dependents {method-function, method-generic-function, method-lambda-list, method-specializers, method-qualifiers} {class-default-initargs, class-direct-default-initargs, class-direct-slots, class-direct-subclasses, class-direct-superclasses, class-finalized-p, class-name, class-precedence-list, class-prototype, class-slots} {slot-definition-allocation, slot-definition-initargs, slot-definition-initform, slot-definition-initfunction, slot-definition-name, slot-definition-type} {slot-definition-readers, slot-definition-writers} {slot-definition-location} remove-dependent remove-direct-method remove-direct-subclass remove-method set-funcallable-instance-function (setf slot-value-using-class) slot-boundp-using-class slot-makunbound-using-class specializer-direct-generic-functions specializer-direct-methods standard-instance-access update-dependent gfs which DO NOT obey AMOP ch 6 accessor-method-slot-definition Not yet defined. Use accessor-method-slot-name and method-specializers to get the direct-slot-definition. compute-applicable-methods compute-applicable-methods-using-classes Handles class-eq specializers without signalling an error. But see "Initialization of method metaobjects", below. compute-discriminating-function [the resulting function works differently different because compute-effective-method is different, and because make-method-lambda does not exist.] compute-effective-method Returns only one value. The utility of bringing this into conformance with AMOP ch 6 is limited by the lack of make-method-lambda. generic-function-argument-precedence-order Not yet defined. Can get this information from the arg-info structure. generic-function-declarations Not yet defined. make-method-lambda Does not exist. This will be hard to add in a way that is compatible with AMOP ch 6. 1. In March 92 PCL, there are two kinds of method-functions. The first kind is what method-function returns (which has no special restrinctions on its use). The second kind is returned by method-function-for-caching, which is used when the wrappers of the required arguments are known. Each call to method-function-for-caching might return a new function. Both kinds of method functions can be closures. March 92 PCL currently uses this scheme to do the pv-lookup ahead of time, thereby eliminating pv caching. (pv-lookup means the lookup of the permutation vectors which are used for fast instance varaible access.) Method function closures can also be used in the optimization of calls to generic-functions, but this has not yet been implemented. 2. Since method-functions can be closures, we would need an extra step between compiling (or coercing) the method-lambda into a function, before it can be applied to arguments with apply or funcall. reader-method-class Not yet defined. Some bootstrapping considerations are involved, but adding this will not be very hard. (setf class-name) Currently just a writer method. Does not call reinitialize-instance or (setf find-class). (setf generic-function-name) Currently just a writer method. Does not call reinitialize-instance. writer-method-class Not yet defined. Some bootstrapping considerations are involved, but adding this will not be very hard. --------------------------- Initialization of method metaobjects The following methods are defined: legal-qualifiers-p (method standard-method) qualifiers legal-lambda-list-p (method standard-method) lambda-list legal-specializers-p (method standard-method) specializers legal-method-function-p (method standard-method) function legal-documentation-p (method standard-method) documentation legal-specializer-p (method standard-method) specializer You can override them if you want. The method for legal-specializers-p calls legal-specializer-p on each specializer. The method for legal-specializer-p allows any kind of specializer when the variable *allow-experimental-specializers-p* is t (this variable is initially nil). --------------------------- Optimizations on slot-value Outside of a defmethod when define-compiler-macro is not implemented or the slot-name is not constant, or Inside a defmethod when the slot-name is not a constant: (1) no optimization of slot-value, slot-value-using-class is called. slot-value-using-class has a special dfun, though, which calls the slot's slot-definition-reader-function. This function is a result of get-accessor-method-function. Outside of a defmethod when define-compiler-macro is implemented and the slot-name is a constant, or Inside a defmethod when the slot-name is a constant but the object is not either (the value of a parameter specialized to a subclass of structure-object for which no user-defined slot-value-using-class methods apply at defmethod time), or (the value of a parameter specialized to a subclass of standard-object). (2) PCL arranges to call an automatically created generic function which has one method: a reader method defined on class slot-object. Inside a defmethod when the slot-name is a constant and the object is (the value of a parameter specialized to a subclass of structure-object for which no user-defined slot-value-using-class methods apply). (3) The slot-value form is converted to a call of the structure slot's accessor function, which the compiler can then optimize further. Inside a defmethod when the slot-name is a constant and the object is (the value of a parameter specialized to a subclass of standard-object). (4) The access requires two arefs, a call to (typep x 'fixnum), and a call to eq, in the best case. If user defined slot-value-using-class methods apply at slot-value execution time, or the slot is unbound, the unoptimized slot-value function (1) is called. This was in May Day PCL; what is new here is that the PV (permutation vector) is looked up at defmethod load time rather than at run time, if the effective method is cached. Generic functions containing only accessor methods for which no user-defined methods on slot-value-using-class apply and which involve only standard-classes: A special dfun is called: one-class, two-class, one-index, or n-n. These were all in May Day PCL. Generic functions excluded by the above, which contain accessor methods: In place of each accessor method's method-function, a function returned by get-accessor-method-function is used. get-accessor-method-function (gf type class slotd) ; type is reader, writer, or boundp. If there is only one applicable method, (This method will be the system supplied one) the function that is returned is optimized for the current state of the class. When the class changes, it must be recomputed. otherwise, a secondary dispatch function for slot-value-using-class is computed (using what is known about the types of the arguments) and converted into an accessor function. get-secondary-dispatch-function (gf methods types &optional method-alist wrappers) The types argument describes what is known about the types of the arguments. Method-alist is used (when supplied) to do things like replace the standard slot-value-using-class method function with a function optimized for what is known about the arguments. Wrappers (when supplied) means that the resulting function is guaranteed to be called only whith those wrappers. Make-effective-method-function calls the generic-function method-function-for-caching with method-alist and wrappers to get a optimized method function. (PV lookup is done at the time method-function-for-caching is called). compute-applicable-methods: Here I tried to stick with the MOP. The function cache-miss-values looks at the second value of the result of compute-applicable-methods-using-classes. If this value is null, we aren't supposed to cache the result of camuc. So we don't. Instead we cache a result of (default-secondary-dispatch-function gf), which in turn calls compute-applicable-methods each time it is called. --------------------------- To do: 1. Make the metaobject protocol function symbols be the external symbols of a package, so they can be used easily. Problem: sometimes there is no need to call a gf's dfun: the emf that is invoked can be cached in the caller's method closure. make-instance is slow. 2. In expand-defmethod-internal, optimize calls to generic-functions. Add the support for this optimization. 3. Make sure that use-dispatch-dfun-p is doing the right thing. 4. [When CMUCL improves its setf handling, remove the comment in the file macros.lisp beginning the line ";#+cmu (pushnew :setf *features*)"] -------------- 1) Generalize expand-defmethod-internal so that it can be used for non-defmethod code. Maybe by (a) having a parameter that says whether it is being called by defmethod, and (b) using the techniques used by the series package (shadowing defun and some others, making the shadowed definitions call e-d-i, making it easy for people to do the relevant package modifications) 2) Extending generic-functions by allowing the user at defgeneric time to supply the name of a function which will be supplied (by the system) with a definition which will return equivalent results to those returned by the generic function, but which will (in some cases) have less checking than the generic function. One-class, two-class, and one-index gf dfuns will map to a result of get-optimized-std-accessor-method-function, checking gf dfuns will map to their function, and any other dfun will remain the same. 3) Extending expand-defmethod-internal to optimize calls to generic-functions. There are a variety of possibilities that need to be considered; one of them will be to arrange to call the optimized functions produced by (2) when it is known to be safe.