belong to the generic function, which is responsible for determining what method or methods to run in response to a particular invocation.
Methods indicate what kinds of arguments they can handle by draw
, you might define one method that specializes the shape
parameter for objects that are instances of the class circle
while another method specializes shape
for objects that are instances of the class triangle
. They would look like this, eliding the actual drawing code:
(defmethod draw ((shape circle))
...)
(defmethod draw ((shape triangle))
...)
When a generic function is invoked, it compares the actual arguments it was passed with the specializers of each of its methods to find the draw
, passing an instance of circle
, the method that specialized shape
on the class circle
is applicable, while if you pass it a triangle
, then the method that specializes shape
on the class triangle
applies. In simple cases, only one method will be applicable, and it will handle the invocation. In more complex cases, there may be multiple methods that apply; they're then combined, as I'll discuss in the section 'Method Combination,' into a single
You can specialize a parameter in two ways—usually you'll specify a class that the argument must be an instance of. Because instances of a class are also considered instances of that class's superclasses, a method with a parameter specialized on a particular class can be applicable whenever the corresponding argument is a direct instance of the specializing class or of any of its subclasses. The other kind of specializer is a so-called EQL
specializer, which specifies a particular object to which the method applies.
When a generic function has only methods specialized on a single parameter and all the specializers are class specializers, the result of invoking a generic function is quite similar to the result of invoking a method in a message-passing system—the combination of the name of the operation and the class of the object on which it's invoked determines what method to run.
However, reversing the order of lookup opens up possibilities not found in message-passing systems. Generic functions support methods that specialize on multiple parameters, provide a framework that makes multiple inheritance much more manageable, and let you use declarative constructs to control how methods are combined into an effective method, supporting several common usage patterns without a lot of boilerplate code. I'll discuss those topics in a moment. But first you need to look at the basics of the two macros used to define the generic functions DEFGENERIC
and DEFMETHOD
.
To give you a feel for these macros and the various facilities they support, I'll show you some code you might write as part of a banking application—or, rather, a toy banking application; the point is to look at a few language features, not to learn how to really write banking software. For instance, this code doesn't even pretend to deal with such issues as multiple currencies let alone audit trails and transactional integrity.
Because I'm not going to discuss how to define new classes until the next chapter, for now you can just assume that certain classes already exist: for starters, assume there's a class bank-account
and that it has two subclasses, checking-account
and savings-account
. The class hierarchy looks like this:
The first generic function will be withdraw
, which decreases the account balance by a specified amount. If the balance is less than the amount, it should signal an error and leave the balance unchanged. You can start by defining the generic function with DEFGENERIC
.
The basic form of DEFGENERIC
is similar to DEFUN
except with no body. The parameter list of DEFGENERIC
specifies the parameters that must be accepted by all the methods that will be defined on the generic function. In the place of the body, a DEFGENERIC
can contain various options. One option you should always include is :documentation
, which you use to provide a string describing the purpose of the generic function. Because a generic function is purely abstract, it's important to be clear to both users and implementers what it's for. Thus, you might define withdraw
like this:
(defgeneric withdraw (account amount)
(:documentation 'Withdraw the specified amount from the account.
Signal an error if the current balance is less than amount.'))
Now you're ready to use DEFMETHOD
to define methods that implement withdraw
.[175]
A method's parameter list must be withdraw
must have exactly two required parameters. More generally, methods must have the same number of required and optional parameters and must be capable of accepting any arguments corresponding to any &rest
or &key
parameters specified by the generic function.[176]
Since the basics of withdrawing are the same for all accounts, you can define a method that specializes the account
parameter on the bank-account
class. You can assume the function balance
returns the current balance of the account and can be used with SETF
—and thus with DECF
—to set the balance. The function ERROR
is a standard function used to signal an error, which I'll discuss in greater detail in Chapter 19. Using those two functions, you can define a basic withdraw
method that looks like this:
(defmethod withdraw ((account bank-account) amount)
(when (< (balance account) amount)
(error 'Account overdrawn.'))
(decf (balance account) amount))
As this code suggests, the form of DEFMETHOD
is even more like that of DEFUN
than DEFGENERIC
's is. The only difference is that the required parameters can be specialized by replacing the parameter name with a two-element list. The first element is the name of the parameter, and the second element is the specializer, either the name of a class or an EQL
specializer, the form of which I'll discuss in a moment. The parameter name can be anything—it doesn't have to match the name used in the generic function, though it often will.