Jean-Philippe Paradis remarks: That's incorrect. defmethod must return the new method, as mandated by the standard. (add-method returns the generic function.)
⚓
The CLOS Specification [X3J13, CLtLII] describes the standard Programmer Interface for the Common Lisp Object System (CLOS). This document extends that specification by defining a metaobject protocol for CLOS---that is, a description of CLOS itself as an extensible CLOS program. In this description, the fundamental elements of CLOS programs (classes, slot definitions, generic functions, methods, specializers and method combinations) are represented by first-class objects. The behavior of CLOS is provided by these objects, or, more precisely, by methods specialized to the classes of these objects.
Because these objects represent pieces of CLOS programs, and because their behavior provides the behavior of the CLOS language itself, they are considered meta-level objects or metaobjects. The protocol followed by the metaobjects to provide the behavior of CLOS is called the CLOS Metaobject Protocol (MOP).
⚓
For each kind of program element there is a corresponding basic metaobject class. These are the classes: class, slot-definition, generic-function, method, and method-combination. A metaobject class is a subclass of exactly one of these classes. The results are undefined if an attempt is made to define a class that is a subclass of more than one basic metaobject class. A metaobject is an instance of a metaobject class.
Each metaobject represents one program element. Associated with each metaobject is the information required to serve its role. This includes information that might be provided directly in a user interface macro such as defclass or defmethod. It also includes information computed indirectly from other metaobjects such as that computed from class inheritance or the full set of methods associated with a generic function.
Much of the information associated with a metaobject is in the form of connections to other metaobjects. This interconnection means that the role of a metaobject is always based on that of other metaobjects. As an introduction to this interconnected structure, this section presents a partial enumeration of the kinds of information associated with each kind of metaobject. More detailed information is presented later.
⚓
A class metaobject determines the structure and the default behavior of its instances. The following information is associated with class metaobjects:
- The name, if there is one, is available as an object.
- The direct subclasses, direct superclasses and class precedence list are available as lists of class metaobjects.
- The slots defined directly in the class are available as a list of direct slot definition metaobjects. The slots which are accessible in instances of the class are available as a list of effective slot definition metaobjects.
- The documentation is available as a string or
nil
. - The methods which use the class as a specializer, and the generic functions associated with those methods are available as lists of method and generic function metaobjects respectively.
⚓
A slot definition metaobject contains information about the definition of a slot. There are two kinds of slot definition metaobjects. A direct slot definition metaobject is used to represent the direct definition of a slot in a class. This corresponds roughly to the slot specifiers found in defclass forms. An effective slot definition metaobject is used to represent information, including inherited information, about a slot which is accessible in instances of a particular class.
Associated with each class metaobject is a list of direct slot definition metaobjects representing the slots defined directly in the class. Also associated with each class metaobject is a list of effective slot definition metaobjects representing the set of slots accessible in instances of that class.
The following information is associated with both direct and effective slot definitions metaobjects:
- The name, allocation, and type are available as forms that could appear in a defclass form.
- The initialization form, if there is one, is available as a form that could appear in a defclass form. The initialization form together with its lexical environment is available as a function of no arguments which, when called, returns the result of evaluating the initialization form in its lexical environment. This is called the initfunction of the slot.
- The slot filling initialization arguments are available as a list of symbols.
- The documentation is available as a string or
nil
.
Certain other information is only associated with direct slot definition metaobjects. This information applies only to the direct definition of the slot in the class (it is not inherited).
- The function names of those generic functions for which there are automatically generated reader and writer methods. This information is available as lists of function names. Any accessors specified in the defclass form are broken down into their equivalent readers and writers in the direct slot definition.
Information, including inherited information, which applies to the definition of a slot in a particular class in which it is accessible is associated only with effective slot definition metaobjects.
- For certain slots, the location of the slot in instances of the class is available.
⚓
A generic function metaobject contains information about a generic function over and above the information associated with each of the generic function's methods.
- The name is available as a function name.
- The methods associated with the generic function are available as a list of method metaobjects.
- The default class for this generic function's method metaobjects is available as a class metaobject.
- The lambda list is available as a list.
- The method combination is available as a method combination metaobject.
- The documentation is available as a string or
nil
. - The argument precedence order is available as a permutation of those symbols from the lambda list which name the required arguments of the generic function.
-
The declarations are available as a list of declarations.
Terminology Note:
There is some ambiguity in Common Lisp about the terms used to identify the various parts of declare special forms. In this document, the term declaration is used to refer to an object that could be an argument to a declare special form. For example, in the special form
(declare (special *g1*))
, the list(special *g1*)
is a declaration.
⚓
A method metaobject contains information about a specific method.
- The qualifiers are available as a list of non-null atoms.
- The lambda list is available as a list.
- The specializers are available as a list of specializer metaobjects.
- The function is available as a function. This function can be applied to arguments and a list of next methods using apply or funcall.
- When the method is associated with a generic function, that generic function metaobject is available. A method can be associated with at most one generic function at a time.
- The documentation is available as a string or
nil
.
⚓
A specializer metaobject represents the specializers of a method. Class metaobjects are themselves specializer metaobjects. A special kind of specializer metaobject is used for eql
specializers.
⚓
A method combination metaobject represents the information about the method combination being used by a generic function.
Note:
This document does not specify the structure of method combination metaobjects.
⚓
The inheritance structure of the specified metaobject classes is shown in Table 5.1.
Table 5.1
Direct superclass relationships among the specified metaobject classes. The class of every class shown is standard-class except for the class T which is an instance of the class built-in-class and the classes generic-function and standard-generic-function which are instances of the class funcallable-standard-class.
Each class marked with a *
is an abstract class and is not intended to be instantiated. The results are undefined if an attempt is made to make an instance of one of these classes with make-instance.
The classes standard-class, standard-direct-slot-definition, standard-effective-slot-definition, standard-method, standard-reader-method, standard-writer-method, and standard-generic-function are called standard metaobject classes. For each kind of metaobject, this is the class the user interface macros presented in the CLOS Specification use by default. These are also the classes on which user specializations are normally based.
The classes built-in-class, funcallable-standard-class and forward-referenced-class are special-purpose class metaobject classes. Built-in classes are instances of the class built-in-class. The class funcallable-standard-class provides a special kind of instances described in the section called Funcallable Instances
. When the definition of a class references another class which has not yet been defined, an instance of forward-referenced-class is used as a stand-in until the class is actually defined.
The class standard-object is the default direct superclass of the class standard-class. When an instance of the class standard-class is created, and no direct superclasses are explicitly specified, it defaults to the class standard-object. In this way, any behavior associated with the class standard-object will be inherited, directly or indirectly, by all instances of the class standard-class. A subclass of standard-class may have a different class as its default direct superclass, but that class must be a subclass of the class standard-object.
The same is true for funcallable-standard-class and funcallable-standard-object.
The class specializer captures only the most basic behavior of method specializers, and is not itself intended to be instantiated. The class class is a direct subclass of specializer reflecting the property that classes by themselves can be used as method specializers. The class eql-specializer is used for eql
specializers.
⚓
The purpose of the Metaobject Protocol is to provide users with a powerful mechanism for extending and customizing the basic behavior of the Common Lisp Object System. As an object-oriented description of the basic CLOS behavior, the Metaobject Protocol makes it possible to create these extensions by defining specialized subclasses of existing metaobject classes.
The Metaobject Protocol provides this capability without interfering with the implementor's ability to develop high-performance implementations. This balance between user extensibility and implementor freedom is mediated by placing explicit restrictions on each. Some of these restrictions are general---they apply to the entire class graph and the applicability of all methods. These are presented in this section.
The following additional terminology is used to present these restrictions:
- Metaobjects are divided into three categories. Those defined in this document are called specified; those defined by an implementation but not mentioned in this document are called implementation-specific; and those defined by a portable program are called portable.
- A class I is interposed between two other classes C1 and C2 if and only if there is some path, following direct superclasses, from the class C1 to the class C2 which includes I.
- A method is specialized to a class if and only if that class is in the list of specializers associated with the method; and the method is in the list of methods associated with some generic function.
- In a given implementation, a specified method is said to have been promoted if and only if the specializers of the method, S1 ... Sn, are defined in this specification as the classes C1 ... Cn, but in the implementation, one or more of the specializers Si, is a superclass of the class given in the specification Ci.
-
For a given generic function and set of arguments, a method M2 extends a method M1 if and only if:
- M1 and M2 are both associated with the given generic function,
- M1 and M2 are both applicable to the given arguments,
- the specializers and qualifiers of the methods are such that when the generic function is called, M2 is executed before M1,
- M1 will be executed if and only if call-next-method is invoked from within the body of M2 and
- call-next-method is invoked from within the body of M2, thereby causing M1 to be executed.
-
For a given generic function and set of arguments, a method M2 overrides a method M1 if and only if conditions i through iv above hold and
- call-next-method is not invoked from within the body of M2, thereby preventing M1 from being executed.
⚓
Implementations are allowed latitude to modify the structure of specified classes and methods. This includes: the interposition of implementation-specific classes; the promotion of specified methods; and the consolidation of two or more specified methods into a single method specialized to interposed classes.
Any such modifications are permitted only so long as for any portable class Cp that is a subclass of one or more specified classes C0 ... Ci, the following conditions are met:
- In the actual class precedence list of Cp, the classes C0 ... Ci must appear in the same order as they would have if no implementation-specific modifications had been made.
- The method applicability of any specified generic function must be the same in terms of behavior as it would have been had no implementation-specific changes been made. This includes specified generic functions that have had portable methods added. In this context, the expression
the same in terms of behavior
means that methods with the same behavior as those specified are applicable, and in the same order. - No portable class Cp may inherit, by virtue of being a direct or indirect subclass of a specified class, any slot for which the name is a symbol accessible in the common-lisp-user package or exported by any package defined in the ANSI Common Lisp standard.
- Implementations are free to define implementation-specific before- and after-methods on specified generic functions. Implementations are also free to define implementation-specific around-methods with extending behavior.
⚓
Portable programs are allowed to define subclasses of specified classes, and are permitted to define methods on specified generic functions, with the following restrictions. The results are undefined if any of these restrictions is violated.
- Portable programs must not redefine any specified classes, generic functions, methods or method combinations. Any method defined by a portable program on a specified generic function must have at least one specializer that is neither a specified class nor an
eql
specializer whose associated value is an instance of a specified class. - Portable programs may define methods that extend specified methods unless the description of the specified method explicitly prohibits this. Unless there is a specific statement to the contrary, these extending methods must return whatever value was returned by the call to call-next-method.
-
Portable programs may define methods that override specified methods only when the description of the specified method explicitly allows this. Typically, when a method is allowed to be overridden, a small number of related methods will need to be overridden as well.
An example of this is the specified methods on the generic functions add-dependent, remove-dependent and map-dependents. Overriding a specified method on one of these generic functions requires that the corresponding method on the other two generic functions be overridden as well.
-
Portable methods on specified generic functions specialized to portable metaobject classes must be defined before any instances of those classes (or any subclasses) are created, either directly or indirectly by a call to make-instance. Methods can be defined after instances are created by allocate-instance however. Portable metaobject classes cannot be redefined.
Implementation Note:
The purpose of this last restriction is to permit implementations to provide performance optimizations by analyzing, at the time the first instance of a metaobject class is initialized, what portable methods will be applicable to it. This can make it possible to optimize calls to those specified generic functions which would have no applicable portable methods.
Note:
The specification technology used in this document needs further development. The concepts of object-oriented protocols and subclass specialization are intuitively familiar to programmers of object-oriented systems; the protocols presented here fit quite naturally into this framework. Nonetheless, in preparing this document, we have found it difficult to give specification-quality descriptions of the protocols in a way that makes it clear what extensions users can and cannot write. Object-oriented protocol specification is inherently about specifying leeway, and this seems difficult using current technology.
⚓
A list in which the first element is one of the symbols defclass, defmethod, defgeneric, define-method-combination, generic-function
, generic-flet
or generic-labels
, and which has proper syntax for that macro is called a user interface macro form. This document provides an extended specification of the defclass, defmethod and defgeneric macros.
The user interface macros defclass, defgeneric and defmethod can be used not only to define metaobjects that are instances of the corresponding standard metaobject class, but also to define metaobjects that are instances of appropriate portable metaobject classes. To make it possible for portable metaobject classes to properly process the information appearing in the macro form, this document provides a limited specification of the processing of these macro forms.
User interface macro forms can be evaluated or compiled and later executed. The effect of evaluating or executing a user interface macro form is specified in terms of calls to specified functions and generic functions which provide the actual behavior of the macro. The arguments received by these functions and generic functions are derived in a specified way from the macro form.
Converting a user interface macro form into the arguments to the appropriate functions and generic functions has two major aspects: the conversion of the macro argument syntax into a form more suitable for later processing, and the processing of macro arguments which are forms to be evaluated (including method bodies).
In the syntax of the defclass macro, the initform and default-initarg-initial-value-form arguments are forms which will be evaluated one or more times after the macro form is evaluated or executed. Special processing must be done on these arguments to ensure that the lexical scope of the forms is captured properly. This is done by building a function of zero arguments which, when called, returns the result of evaluating the form in the proper lexical environment.
In the syntax of the defmethod macro the form* argument is a list of forms that comprise the body of the method definition. This list of forms must be processed specially to capture the lexical scope of the macro form. In addition, the lexical functions available only in the body of methods must be introduced. To allow this and any other special processing (such as slot access optimization), a specializable protocol is used for processing the body of methods. This is discussed in the section Processing Method Bodies
.
⚓
It is common practice for Common Lisp compilers, while processing a file or set of files, to maintain information about the definitions that have been compiled so far. Among other things, this makes it possible to ensure that a global macro definition (defmacro form) which appears in a file will affect uses of the macro later in that file. This information about the state of the compilation is called the compile-file environment.
When compiling files containing CLOS definitions, it is useful to maintain certain additional information in the compile-file environment. This can make it possible to issue various kinds of warnings (e.g., lambda list congruence) and to do various performance optimizations that would not otherwise be possible.
At this time, there is such significant variance in the way existing Common Lisp implementations handle compile-file environments that it would be premature to specify this mechanism. Consequently, this document specifies only the behavior of evaluating or executing user interface macro forms. What functions and generic functions are called during compile-file processing of a user interface macro form is not specified. Implementations are free to define and document their own behavior. Users may need to check implementation-specific behavior before attempting to compile certain portable programs.
⚓
The evaluation or execution of a defclass form results in a call to the ensure-class function. The arguments received by ensure-class are derived from the defclass form in a defined way. The exact macro-expansion of the defclass form is not defined, only the relationship between the arguments to the defclass macro and the arguments received by the ensure-class function. Examples of typical defclass forms and sample expansions are shown in Figures 5.1 and 5.2.
- The name argument to defclass becomes the value of the first argument to ensure-class. This is the only positional argument accepted by ensure-class; all other arguments are keyword arguments.
- The direct-superclasses argument to defclass becomes the value of the
:direct-superclasses
keyword argument to ensure-class. -
The direct-slots argument to defclass becomes the value of the
:direct-slots
keyword argument to ensure-class. Special processing of this value is done to regularize the form of each slot specification and to properly capture the lexical scope of the initialization forms. This is done by converting each slot specification to a property list called a canonicalized slot specification. The resulting list of canonicalized slot specifications is the value of the:direct-slots
keyword argument.Canonicalized slot specifications are later used as the keyword arguments to a generic function which will, in turn, pass them to make-instance for use as a set of initialization arguments. Each canonicalized slot specification is formed from the corresponding slot specification as follows:
- The name of the slot is the value of the
:name
property. This property appears in every canonicalized slot specification. -
When the
:initform
slot option is present in the slot specification, then both the:initform
and:initfunction
properties are present in the canonicalized slot specification. The value of the:initform
property is the initialization form. The value of the:initfunction
property is a function of zero arguments which, when called, returns the result of evaluating the initialization form in its proper lexical environment.If the
:initform
slot option is not present in the slot specification, then either the:initfunction
property will not appear, or its value will be false. In such cases, the value of the:initform
property, or whether it appears, is unspecified. - The value of the
:initargs
property is a list of the values of each:initarg
slot option. If there are no:initarg
slot options, then either the:initargs
property will not appear or its value will be the empty list. -
The value of the
:readers
property is a list of the values of each:reader
and:accessor
slot option. If there are no:reader
or:accessor
slot options, then either the:readers
property will not appear or its value will be the empty list. - The value of the
:writers
property is a list of the values specified by each:writer
and:accessor
slot option. The value specified by a:writer
slot option is just the value of the slot option. The value specified by an:accessor
slot option is a two element list: the first element is the symbolsetf
, the second element is the value of the slot option. If there are no:writer
or:accessor
slot options, then either the:writers
property will not appear or its value will be the empty list. - The value of the
:documentation
property is the value of the:documentation
slot option. If there is no:documentation
slot option, then either the:documentation
property will not appear or its value will be false. - All other slot options appear as the values of properties with the same name as the slot option. Note that this includes not only the remaining standard slot options (
:allocation
and:type
), but also any other options and values appearing in the slot specification. If one of these slot options appears more than once, the value of the property will be a list of the specified values. - An implementation is free to add additional properties to the canonicalized slot specification provided these are not symbols accessible in the common-lisp-user package, or exported by any package defined in the ANSI Common Lisp standard.
- The name of the slot is the value of the
Returning to the correspondence between arguments to the defclass macro and the arguments received by the ensure-class function:
-
The default-initargs class option, if it is present in the defclass form, becomes the value of the
:direct-default-initargs
keyword argument to ensure-class. Special processing of this value is done to properly capture the lexical scope of the default value forms. This is done by converting each default initarg in the class option into a canonicalized default initarg. The resulting list of canonicalized default initargs is the value of the:direct-default-initargs
keyword argument to ensure-class.A canonicalized default initarg is a list of three elements. The first element is the name; the second is the actual form itself; and the third is a function of zero arguments which, when called, returns the result of evaluating the default value form in its proper lexical environment.
- The metaclass class option, if it is present in the defclass form, becomes the value of the
:metaclass
keyword argument to ensure-class. - The documentation class option, if it is present in the defclass form, becomes the value of the
:documentation
keyword argument to ensure-class. - Any other class options become the value of keyword arguments with the same name. The value of the keyword argument is the tail of the class option. An error is signaled if any class option appears more than once in the defclass form.
In the call to ensure-class, every element of its arguments appears in the same left-to-right order as the corresponding element of the defclass form, except that the order of the properties of canonicalized slot specifications is unspecified. The values of properties in canonicalized slot specifications do follow this ordering requirement. Other ordering relationships in the keyword arguments to ensure-class are unspecified.
The result of the call to ensure-class is returned as the result of evaluating or executing the defclass form.
⚓
The evaluation or execution of a defmethod form requires first that the body of the method be converted to a method function. This process is described in the next section. The result of this process is a method function and a set of additional initialization arguments to be used when creating the new method. Given these two values, the evaluation or execution of a defmethod form proceeds in three steps.
The first step ensures the existence of a generic function with the specified name. This is done by calling the function ensure-generic-function. The first argument in this call is the generic function name specified in the defmethod form.
The second step is the creation of the new method metaobject by calling make-instance. The class of the new method metaobject is determined by calling generic-function-method-class on the result of the call to ensure-generic-function from the first step.
The initialization arguments received by the call to make-instance are as follows:
- The value of the :qualifiers initialization argument is a list of the qualifiers which appeared in the defmethod form. No special processing is done on these values. The order of the elements of this list is the same as in the defmethod form.
- The value of the :lambda-list initialization argument is the unspecialized lambda list from the defmethod form.
- The value of the :specializers initialization argument is a list of the specializers for the method. For specializers which are classes, the specializer is the class metaobject itself. In the case of
eql
specializers, it will be an eql-specializer metaobject obtained by calling intern-eql-specializer on the result of evaluating theeql
specializer form in the lexical environment of the defmethod form. - The value of the :function initialization argument is the method function.
- The value of the :declarations initialization argument is a list of the declarations from the defmethod form. If there are no declarations in the macro form, this initialization argument either doesn't appear, or appears with a value of the empty list.
- The value of the :documentation initialization argument is the documentation string from the defmethod form. If there is no documentation string in the macro form this initialization argument either doesn't appear, or appears with a value of false.
- Any other initialization argument produced in conjunction with the method function are also included.
- The implementation is free to include additional initialization arguments provided these are not symbols accessible in the common-lisp-user package, or exported by any package defined in the ANSI Common Lisp standard.
In the third step, add-method is called to add the newly created method to the set of methods associated with the generic function metaobject.
The result of the call to add-method is returned as the result of evaluating or executing the defmethod form.
An example showing a typical defmethod form and a sample expansion is shown in Figure 5.3. The processing of the method body for this method is shown in Figure 5.4.
⚓
Before a method can be created, the list of forms comprising the method body must be converted to a method function. This conversion is a two step process.
Note:
The body of methods can also appear in the :initial-methods
option of defgeneric forms. Initial methods are not considered by any of the protocols specified in this document.
The first step occurs during macro-expansion of the macro form. In this step, the method lambda list, declarations and body are converted to a lambda expression called a method lambda. This conversion is based on information associated with the generic function definition in effect at the time the macro form is expanded.
The generic function definition is obtained by calling ensure-generic-function with a first argument of the generic function name specified in the macro form. The :lambda-list
keyword argument is not passed in this call.
Given the generic function, production of the method lambda proceeds by calling make-method-lambda. The first argument in this call is the generic function obtained as described above. The second argument is the result of calling class-prototype on the result of calling generic-function-method-class on the generic function. The third argument is a lambda expression formed from the method lambda list, declarations and body. The fourth argument is the macro-expansion environment of the macro form; this is the value of the &environment
argument to the defmethod macro.
The generic function make-method-lambda returns two values. The first is the method lambda itself. The second is a list of initialization arguments and values. These are included in the initialization arguments when the method is created.
In the second step, the method lambda is converted to a function which properly captures the lexical scope of the macro form. This is done by having the method lambda appear in the macro-expansion as the argument of the function special form. During the subsequent evaluation of the macro-expansion, the result of the function special form is the method function.
⚓
The evaluation or execution of a defgeneric form results in a call to the ensure-generic-function function. The arguments received by ensure-generic-function are derived from the defgeneric form in a defined way. As with defclass and defmethod, the exact macro-expansion of the defgeneric form is not defined, only the relationship between the arguments to the macro and the arguments received by ensure-generic-function.
- The function-name argument to defgeneric becomes the first argument to ensure-generic-function. This is the only positional argument accepted by ensure-generic-function; all other arguments are keyword arguments.
- The lambda-list argument to defgeneric becomes the value of the
:lambda-list
keyword argument to ensure-generic-function. - For each of the options
:argument-precedence-order
,:documentation
,:generic-function-class
and:method-class
, the value of the option becomes the value of the keyword argument with the same name. If the option does not appear in the macro form, the keyword argument does not appear in the resulting call to ensure-generic-function. - For the option
declare
, the list of declarations becomes the value of the:declarations
keyword argument. If thedeclare
option does not appear in the macro form, the:declarations
keyword argument does not appear in the call to ensure-generic-function. - The handling of the
:method-combination
option is not specified.
The result of the call to ensure-generic-function is returned as the result of evaluating or executing the defgeneric form.
⚓
This section provides an overview of the Metaobject Protocols. The detailed behavior of each function, generic function and macro in the Metaobject Protocol is presented in Chapter 6. The remainder of this chapter is intended to emphasize connections among the parts of the Metaobject Protocol, and to provide some examples of the kinds of specializations and extensions the protocols are designed to support.
⚓
Like other objects, metaobjects can be created by calling make-instance. The initialization arguments passed to make-instance are used to initialize the metaobject in the usual way. The set of legal initialization arguments, and their interpretation, depends on the kind of metaobject being created. Implementations and portable programs are free to extend the set of legal initialization arguments. Detailed information about the initialization of each kind of metaobject are provided in Chapter 6; this section provides an overview and examples of this behavior.
⚓
Class metaobjects created with make-instance are usually anonymous; that is, they have no proper name. An anonymous class metaobject can be given a proper name using (setf find-class) and (setf class-name).
When a class metaobject is created with make-instance, it is initialized in the usual way. The initialization arguments passed to make-instance are used to establish the definition of the class. Each initialization argument is checked for errors and associated with the class metaobject. The initialization arguments correspond roughly to the arguments accepted by the defclass macro, and more closely to the arguments accepted by the ensure-class function.
Some class metaobject classes allow their instances to be redefined. When permissible, this is done by calling reinitialize-instance. This is discussed in the next section.
An example of creating an anonymous class directly using make-instance follows:
(flet ((zero () 0) (propellor () *propellor*)) (make-instance 'standard-class :name '(my-class foo) :direct-superclasses (list (find-class 'plane) another-anonymous-class) :direct-slots `((:name x :initform 0 :initfunction ,#'zero :initargs (:x) :readers (position-x) :writers ((setf position-x))) (:name y :initform 0 :initfunction ,#'zero :initargs (:y) :readers (position-y) :writers ((setf position-y)))) :direct-default-initargs `((:engine *propellor* ,#'propellor))))
Robert Strandh remarks: This section is named Initialization of Class Metaobjects and appears in Chapter 5 (Concepts) of the original text. There is a section with the same name in Chapter 6 (Generic functions and methods) of the original text. When sections are referred to in the text, it is not specified which one.
⚓
Some class metaobject classes allow their instances to be reinitialized. This is done by calling reinitialize-instance. The initialization arguments have the same interpretation as in class initialization.
If the class metaobject was finalized before the call to reinitialize-instance, finalize-inheritance will be called again once all the initialization arguments have been processed and associated with the class metaobject. In addition, once finalization is complete, any dependents of the class metaobject will be updated by calling update-dependent.
⚓
An example of creating a generic function and a method metaobject, and then adding the method to the generic function is shown below. This example is comparable to the method definition shown in Figure 5.3.
(let* ((gf (make-instance 'standard-generic-function :lambda-list '(p l &optional visiblyp &key))) (method-class (generic-function-method-class gf))) (multiple-value-bind (lambda initargs) (make-method-lambda gf (class-prototype method-class) '(lambda (p l &optional (visiblyp t) &key color) (set-to-origin p) (when visiblyp (show-move p 0 color))) nil) (add-method gf (apply #'make-instance method-class :function (compile nil lambda) :specializers (list (find-class 'position) (intern-eql-specializer 0)) :qualifiers () :lambda-list '(p l &optional (visiblyp t) &key color) initargs))))
⚓
Class finalization is the process of computing the information a class inherits from its superclasses and preparing to actually allocate instances of the class. The class finalization process includes computing the class' class precedence list, the full set of slots accessible in instances of the class and the full set of default initialization arguments for the class. These values are associated with the class metaobject and can be accessed by calling the appropriate reader. In addition, the class finalization process makes decisions about how instances of the class will be implemented.
To support forward-referenced superclasses, and to account for the fact that not all classes are actually instantiated, class finalization is not done as part of the initialization of the class metaobject. Instead, finalization is done as a separate protocol, invoked by calling the generic function finalize-inheritance. The exact point at which finalize-inheritance is called depends on the class of the class metaobject; for standard-class it is called sometime after all the class' superclasses are defined, but no later than when the first instance of the class is allocated (by allocate-instance).
The first step of class finalization is computing the class precedence list. Doing this first allows subsequent steps to access the class precedence list. This step is performed by calling the generic function compute-class-precedence-list. The value returned from this call is associated with the class metaobject and can be accessed by calling the class-precedence-list generic function.
The second step is computing the full set of slots that will be accessible in instances of the class. This step is performed by calling the generic function compute-slots. The result of this call is a list of effective slot definition metaobjects. This value is associated with the class metaobject and can be accessed by calling the class-slots generic function.
The behavior of compute-slots is itself layered, consisting of calls to effective-slot-definition-class and compute-effective-slot-definition.
The final step of class finalization is computing the full set of initialization arguments for the class. This is done by calling the generic function compute-default-initargs. The value returned by this generic function is associated with the class metaobject and can be accessed by calling class-default-initargs.
If the class was previously finalized, finalize-inheritance may call make-instances-obsolete. The circumstances under which this happens are described in the section of the CLOS specification called Redefining Classes
.
Forward-referenced classes, which provide a temporary definition for a class which has been referenced but not yet defined, can never be finalized. An error is signaled if finalize-inheritance is called on a forward-referenced class.
⚓
The instance structure protocol is responsible for implementing the behavior of the slot access functions like slot-value and (setf slot-value).
For each CLOS slot access function other than slot-exists-p, there is a corresponding generic function which actually provides the behavior of the function. When called, the slot access function finds the pertinent effective slot definition metaobject, calls the corresponding generic function and returns its result. The arguments passed on to the generic function include one additional value, the class of the object argument, which always immediately precedes the object argument.
The correspondences between slot access function and underlying slot access generic function are as follows:
[Table 5.2]
Slot access function | Corresponding slot access generic function |
---|---|
slot-boundp | slot-boundp-using-class |
slot-makunbound | slot-makunbound-using-class |
slot-value | slot-value-using-class |
(setf slot-value) | (setf slot-value-using-class) |
At the lowest level, the instance structure protocol provides only limited mechanisms for portable programs to control the implementation of instances and to directly access the storage associated with instances without going through the indirection of slot access. This is done to allow portable programs to perform certain commonly requested slot access optimizations.
In particular, portable programs can control the implementation of, and obtain direct access to, slots with allocation :instance
and type T. These are called directly accessible slots.
The relevant specified around-method on compute-slots determines the implementation of instances by deciding how each slot in the instance will be stored. For each directly accessible slot, this method allocates a location and associates it with the effective slot definition metaobject. The location can be accessed by calling the slot-definition-location generic function. Locations are non-negative integers. For a given class, the locations increase consecutively, in the order that the directly accessible slots appear in the list of effective slots. (Note that here, the next paragraph, and the specification of this around-method are the only places where the value returned by compute-slots is described as a list rather than a set.)
Given the location of a directly accessible slot, the value of that slot in an instance can be accessed with the appropriate accessor. For standard-class, this accessor is the function standard-instance-access. For funcallable-standard-class, this accessor is the function funcallable-standard-instance-access. In each case, the arguments to the accessor are the instance and the slot location, in that order. See the definition of each accessor in Chapter 6 for additional restrictions on the use of these functions.
Portable programs are permitted to affect and rely on the allocation of locations only in the following limited way: By first defining a portable primary method on compute-slots which orders the returned value in a predictable way, and then relying on the defined behavior of the specified around-method to assign locations to all directly accessible slots. Portable programs may compile-in calls to low-level accessors which take advantage of the resulting predictable allocation of slot locations.
Example:
The following example shows the use of this mechanism to implement a new class metaobject class, ordered-class
and class option :slot-order
. This option provides control over the allocation of slot locations. In this simple example implementation, the :slot-order
option is not inherited by subclasses; it controls only instances of the class itself.
(defclass ordered-class (standard-class)
((slot-order :initform ()
:initarg :slot-order
:reader class-slot-order)))
(defmethod compute-slots ((class ordered-class))
(let ((order (class-slot-order class)))
(sort (copy-list (call-next-method))
#'(lambda (a b)
(< (position (slot-definition-name a) order)
(position (slot-definition-name b) order))))))
Following is the source code the user of this extension would write. Note that because the code above doesn't implement inheritance of the :slot-order
option, the function distance
must not be called on instances of subclasses of point
; it can only be called on instances of point
itself.
(defclass point () ((x :initform 0) (y :initform 0)) (:metaclass ordered-class) (:slot-order x y)) (defun distance (point) (sqrt (/ (+ (expt (standard-instance-access point 0) 2) (expt (standard-instance-access point 1) 2)) 2.0)))
In more realistic uses of this mechanism, the calls to the low-level instance structure accessors would not actually appear textually in the source program, but rather would be generated by a meta-level analysis program run during the process of compiling the source program.
⚓
Instances of classes which are themselves instances of funcallable-standard-class or one of its subclasses are called funcallable instances. Funcallable instances can only be created by allocate-instance (class funcallable-standard-class).
Like standard instances, funcallable instances have slots with the normal behavior. They differ from standard instances in that they can be used as functions as well; that is, they can be passed to funcall and apply, and they can be stored as the definition of a function name. Associated with each funcallable instance is the function which it runs when it is called. This function can be changed with set-funcallable-instance-function.
Example:
The following simple example shows the use of funcallable instances to create a simple, defstruct-like facility. (Funcallable instances are useful when a program needs to construct and maintain a set of functions and information about those functions. They make it possible to maintain both as the same object rather than two separate objects linked, for example, by hash tables.)
(defclass constructor ()
((name :initarg :name :accessor constructor-name)
(fields :initarg :fields :accessor constructor-fields))
(:metaclass funcallable-standard-class))
(defmethod initialize-instance :after ((c constructor) &key)
(with-slots (name fields) c
(set-funcallable-instance-function
c
#'(lambda ()
(let ((new (make-array (1+ (length fields)))))
(setf (aref new 0) name)
new)))))
(setq c1 (make-instance 'constructor
:name 'position :fields '(x y)))
#<CONSTRUCTOR 262437>
(setq p1 (funcall c1))
#<ARRAY 3 263674>
⚓
Associated with each generic function is its discriminating function. Each time the generic function is called, the discriminating function is called to provide the behavior of the generic function. The discriminating function receives the full set of arguments received by the generic function. It must lookup and execute the appropriate methods, and return the appropriate values.
The discriminating function is computed by the highest layer of the generic function invocation protocol, compute-discriminating-function. Whenever a generic function metaobject is initialized, reinitialized, or a method is added or removed, the discriminating function is recomputed. The new discriminating function is then stored with set-funcallable-instance-function.
Discriminating functions call compute-applicable-methods and compute-applicable-methods-using-classes to compute the methods applicable to the generic functions arguments. Applicable methods are combined by compute-effective-method to produce an effective method. Provisions are made to allow memoization of the method applicability and effective methods computations. (See the description of compute-discriminating-function for details.)
The body of method definitions are processed by make-method-lambda. The result of this generic function is a lambda expression which is processed by either compile or the file compiler to produce a method function. The arguments received by the method function are controlled by the call-method forms appearing in the effective methods. By default, method functions accept two arguments: a list of arguments to the generic function, and a list of next methods. The list of next methods corresponds to the next methods argument to call-method. If call-method appears with additional arguments, these will be passed to the method functions as well; in these cases, make-method-lambda must have created the method lambdas to expect additional arguments.
⚓
It is convenient for portable metaobjects to be able to memoize information about other metaobjects, portable or otherwise. Because class and generic function metaobjects can be reinitialized, and generic function metaobjects can be modified by adding and removing methods, a means must be provided to update this memoized information.
The dependent maintenance protocol supports this by providing a way to register an object which should be notified whenever a class or generic function is modified. An object which has been registered this way is called a dependent of the class or generic function metaobject. The dependents of class and generic function metaobjects are maintained with add-dependent and remove-dependent. The dependents of a class or generic function metaobject can be accessed with map-dependents. Dependents are notified about a modification by calling update-dependent. (See the specification of update-dependent for detailed description of the circumstances under which it is called.)
To prevent conflicts between two portable programs, or between portable programs and the implementation, portable code must not register metaobjects themselves as dependents. Instead, portable programs which need to record a metaobject as a dependent, should encapsulate that metaobject in some other kind of object, and record that object as the dependent. The results are undefined if this restriction is violated.
Example:
This example shows a general facility for encapsulating metaobjects before recording them as dependents. The facility defines a basic kind of encapsulating object: an updater. Specializations of the basic class can be defined with appropriate special updating behavior. In this way, information about the updating required is associated with each updater rather than with the metaobject being updated.
Updaters are used to encapsulate any metaobject which requires updating when a given class or generic function is modified. The function record-updater is called to both create an updater and add it to the dependents of the class or generic function. Methods on the generic function update-dependent, specialized to the specific class of updater do the appropriate update work.
(defclass updater ()
((dependent :initarg :dependent :reader dependent)))
(defun record-updater (class dependee dependent &rest initargs)
(let ((updater (apply #'make-instance class :dependent dependent
initargs)))
(add-dependent dependee updater)
updater))
A flush-cache-updater
simply flushes the cache of the dependent when it is updated.
(defclass flush-cache-updater (updater) ())
(defmethod update-dependent (dependee (updater flush-cache-updater)
&rest args)
(declare (ignore args))
(flush-cache (dependent updater)))