EQL
specializer. The comparison of those methods will thus be decided based on other parameters.
Now that you understand how the applicable methods are found and sorted, you're ready to take a closer look at the last step—how the sorted list of methods is combined into a single effective method. By default, generic functions use what's called the CALL-NEXT-METHOD
works as you've already seen—the most specific method runs first, and each method can pass control to the next most specific method via CALL-NEXT-METHOD
.
However, there's a bit more to it than that. The methods I've been discussing so far are called :before
, :after
, and :around
methods. An auxiliary method definition is written with DEFMETHOD
like a primary method but with a :before
method on withdraw
that specializes the account
parameter on the class bank-account
would start like this:
(defmethod withdraw :before ((account bank-account) amount) ...)
Each kind of auxiliary method is combined into the effective method in a different way. All the applicable :before
methods—not just the most specific—are run as part of the effective method. They run, as their name suggests, before the most specific primary method and are run in most-specific-first order. Thus, :before
methods can be used to do any preparation needed to ensure that the primary method can run. For instance, you could've used a :before
method specialized on checking- account
to implement the overdraft protection on checking accounts like this:
(defmethod withdraw :before ((account checking-account) amount)
(let ((overdraft (- amount (balance account))))
(when (plusp overdraft)
(withdraw (overdraft-account account) overdraft)
(incf (balance account) overdraft))))
This :before
method has three advantages over a primary method. One is that it makes it immediately obvious how the method changes the overall behavior of the withdraw
function—it's not going to interfere with the main behavior or change the result returned.
The next advantage is that a primary method specialized on a class more specific than checking- account
won't interfere with this :before
method, making it easier for an author of a subclass of checking-account
to extend the behavior of withdraw
while keeping part of the old behavior.
Lastly, since a :before
method doesn't have to call CALL-NEXT- METHOD
to pass control to the remaining methods, it's impossible to introduce a bug by forgetting to.
The other auxiliary methods also fit into the effective method in ways suggested by their names. All the :after
methods run after the primary methods in most-specific-last order, that is, the reverse of the :before
methods. Thus, the :before
and :after
methods combine to create a sort of nested wrapping around the core functionality provided by the primary methods—each more-specific :before
method will get a chance to set things up so the less-specific :before
methods and primary methods can run successfully, and each more-specific :after
method will get a chance to clean up after all the primary methods and less-specific :after
methods.
Finally, :around
methods are combined much like primary methods except they're run 'around' all the other methods. That is, the code from the most specific :around
method is run before anything else. Within the body of an :around
method, CALL-NEXT- METHOD
will lead to the code of the next most specific :around
method or, in the least specific :around
method, to the complex of :before
, primary, and :after
methods. Almost all :around
methods will contain such a call to CALL-NEXT-METHOD
because an :around
method that doesn't will completely hijack the implementation of the generic function from all the methods except for more-specific :around
methods.
Occasionally that kind of hijacking is called for, but typically :around
methods are used to establish some dynamic context in which the rest of the methods will run—to bind a dynamic variable, for example, or to establish an error handler (as I'll discuss in Chapter 19). About the only time it's appropriate for an :around
method to not call CALL-NEXT-METHOD
is when it returns a result cached from a previous call to CALL-NEXT-METHOD
. At any rate, an :around
method that doesn't call CALL-NEXT- METHOD
is responsible for correctly implementing the semantics of the generic function for all classes of arguments to which the method may apply, including future subclasses.
Auxiliary methods are just a convenient way to express certain common patterns more concisely and concretely. They don't actually allow you to do anything you couldn't do by combining primary methods with diligent adherence to a few coding conventions and some extra typing. Perhaps their biggest benefit is that they provide a uniform framework for extending generic functions. Often a library will define a generic function and provide a default primary method, allowing users of the library to customize its behavior by defining appropriate auxiliary methods.
In addition to the standard method combination, the language specifies nine other built-in method combinations known as the
All the simple combinations follow the same pattern: instead of invoking the most specific primary method and letting it invoke less-specific primary methods via CALL-NEXT-METHOD
, the simple method combinations produce an effective method that contains the code of all the primary methods, one after another, all wrapped in a call to the function, macro, or special operator that gives the method combination its name. The nine combinations are named for the operators: +
, AND
, OR
, LIST
, APPEND
, NCONC
, MIN
, MAX
, and PROGN
. The simple combinations also support only two kinds of methods, primary methods, which are combined as just described, and :around
methods, which work like :around
methods in the standard method combination.
For example, a generic function that uses the +
method combination will return the sum of all the results returned by its primary methods. Note that the AND
and OR
method combinations won't