Chapter 6
Functions
Method Dispatch
When a generic function is called, the generic function uses the types of the arguments to determine which methods to call. This process is called method dispatch.
Method dispatch occurs in three phases. First, all the applicable methods are selected. Next, the applicable methods are sorted by specificity. Finally, the most specific method is called.
Method Specificity
For any two methods A and B that are applicable to a given generic function call, one method may be more specific than the other, or the methods may be ambiguous methods.
To order two methods A and B with respect to a particular set of arguments, compare each of A's specializers with B's specializer in the corresponding position using the argument that was supplied for that position. The comparison works in the following way.
- If the specializers are type equivalent, then A and B are unordered at the current argument position. That is, this argument position provides no information about the order of the two methods.
- Otherwise, if the specializer of A is a subtype of the specializer of B, then A precedes B at the current argument position.
- Otherwise, if both specializers are classes, then their order in the class precedence list of the argument's class is used to determine which is more specific. If A's specializer precedes B's specializer in the class precedence list of the argument's class, then A precedes B at the current argument position.
- Otherwise, the methods are unordered in the current argument position.
The method A is more specific than the method B if and only if A precedes B in at least one argument position, and B does not precede A in any argument position. Similarly, B is more specific than A if and only if B precedes A in at least one argument position, and A does not precede B in any argument position. If neither of these cases apply then A and B are ambiguous methods.
When the applicable methods are sorted by specificity, the sorted list is divided into two parts, each possibly empty. The first part contains methods that are more specific than every method that follows them. The second part (which cannot itself be sorted) begins at the first point of ambiguity; there are at least two methods that could equally well go first in the second part. When a generic function is called, if the first part of the sorted applicable methods is empty then an error is signaled. Similarly, if the last method in the first part attempts to call next-method, an error is signaled.
Consider the following class definitions:
define class <sentient> (<life-form>) end class; define class <bipedal> (<life-form>) end class; define class <intelligent> (<sentient>) end class; define class <humanoid> (<bipedal>) end class; define class <vulcan> (<intelligent>, <humanoid>) end class;
Computing the class precedence list for <vulcan>
yields
#(<vulcan>,<intelligent>,<sentient>,<humanoid>,<bipedal>,<life-form>)
The class precedence lists computed for two different classes may have different precedence
orders for some intermediate superclasses. This is not a problem as long as there is no class
that inherits from both classes. For example, we could define a
class <human>
as follows:
define class <human> (<humanoid>, <intelligent>) end class;
For the class <human>
defined as above, the class precedence list would
be
#(<human>,<humanoid>,<bipedal>,<intelligent>,<sentient>,<life-form>)
It is not a problem that the two class precedence lists give different orders to some of
the intermediate superclasses such as <bipedal>
and <sentient>
as long as no class is added that inherits from
both <vulcan>
and <human>
.
When sorting the applicable methods, each specializer needs to be viewed with respect to the class precedence list for the class of the argument passed to the generic function in that argument position. For example, given the following definitions
define method psychoanalyze (being :: <intelligent>) … end method; define method psychoanalyze (being :: <humanoid>) … end method;
calling the generic function psychoanalyze
on a being of
type <human>
would cause the method for <humanoid>
to
be called first, while calling the generic function on a being of
type <vulcan>
would cause the method for <intelligent>
to be called first.
The order of arguments is not significant when computing method
specificity. Given the above class definitions, the following methods are unambiguous
when their generic function is called on two beings of type <vulcan>
or
two beings of type <human>
, but the methods are ambiguous when the call
includes one being of type <vulcan>
and one of
type <human>
.
define method superior-being (a :: <intelligent>, b :: <intelligent>) most-intelligent-being (a, b) end method; define method superior-being (a :: <humanoid>, b :: <humanoid>) best-looking-being (a, b) end method;
Calling Less Specific Methods
In many situations, a subtype wants to modify the behavior of a method, rather than replace
it completely; it wants to perform some work but also use the inherited behavior. This can
be accomplished with next-method. Next-method is a function that, when called,
invokes the next most specific method applicable in the generic function. The next-method is
the value of the #next
parameter. Normally this parameter is
named next-method
, though it can have other names at the programmer's
discretion.
One can think of next-method as invoking the method that would have been called if the current method did not exist.
If there are no more methods available, the next-method parameter will be bound to the
value #f
instead of to a method.
Passing Different Arguments to Next-Method
In the usual case, next-method is called with no arguments. This indicates that the next-method should be passed the same arguments that were supplied to the current method.
It is valid to supply arguments, including different arguments, when calling next-method. However, if you pass different arguments, the new arguments must result in the same ordered sequence of applicable methods as the original arguments. Otherwise, the program behavior is undefined.
In some cases, the methods in a generic function accept
different keyword arguments. In such cases, it's convenient for the methods also to accept a
rest parameter. That way, all the keyword/value pairs passed to the generic function are
captured in the rest parameter. By using apply
, the next-method can be invoked
with the complete set of arguments. (This technique is only necessary, of course, when the
method calls next-method and passes arguments explicitly.)
As usual, if there are duplicates of a given keyword argument, the leftmost occurrence is used. This allows keyword arguments to be easily overridden.
The Next-Method Parameter
The value of the next-method parameter is supplied by the generic function dispatch mechanism. When a method is called by its generic function, the generic function dispatch mechanism automatically passes the appropriate value for next-method. There is no way for a user program to specify the next-method argument when calling a method.
If you create a method directly (i.e., with method
rather than
with define method
) and you want this method to accept a next-method parameter,
then you should insert a #next
into the parameter list explicitly. You would do
this if you are creating a method that you plan to add to a generic function, and you want
this method to be able to call next-method. You can also supply the next-method parameter
when using define method
, in cases where you want to give the parameter a
different name.