This method will apply whenever the first argument to withdraw
is an instance of bank-account
. The second parameter, amount
, is implicitly specialized on T
, and since all objects are instances of T
, it doesn't affect the applicability of the method.
Now suppose all checking accounts have overdraft protection. That is, each checking account is linked to another bank account that's drawn upon when the balance of the checking account itself can't cover a withdrawal. You can assume that the function overdraft-account
takes a checking-account
object and returns a bank-account
object representing the linked account.
Thus, withdrawing from a checking-account
object requires a few extra steps compared to withdrawing from a standard bank-account
object. You must first check whether the amount being withdrawn is greater than the account's current balance and, if it is, transfer the difference from the overdraft account. Then you can proceed as with a standard bank-account
object.
So what you'd like to do is define a method on withdraw
that specializes on checking-account
to handle the transfer and then lets the method specialized on bank- account
take control. Such a method might look like this:
(defmethod withdraw ((account checking-account) amount)
(let ((overdraft (- amount (balance account))))
(when (plusp overdraft)
(withdraw (overdraft-account account) overdraft)
(incf (balance account) overdraft)))
(call-next-method))
The function CALL-NEXT-METHOD
is part of the generic function machinery used to combine applicable methods. It indicates that control should be passed from this method to the method specialized on bank-account
.[177] When it's called with no arguments, as it is here, the next method is invoked with whatever arguments were originally passed to the generic function. It can also be called with arguments, which will then be passed onto the next method.
You aren't required to invoke CALL-NEXT-METHOD
in every method. However, if you don't, the new method is then responsible for completely implementing the desired behavior of the generic function. For example, if you had a subclass of bank-account
, proxy- account
, that didn't actually keep track of its own balance but instead delegated withdrawals to another account, you might write a method like this (assuming a function, proxied-account
, that returns the proxied account):
(defmethod withdraw ((proxy proxy-account) amount)
(withdraw (proxied-account proxy) amount))
Finally, DEFMETHOD
also allows you to create methods specialized on a particular object with an EQL
specializer. For example, suppose the banking app is going to be deployed in a particularly corrupt bank. Suppose the variable *account-of-bank- president*
holds a reference to a particular bank account that belongs—as the name suggests—to the bank's president. Further suppose the variable *bank*
represents the bank as a whole, and the function embezzle
steals money from the bank. The bank president might ask you to 'fix' withdraw
to handle his account specially.
(defmethod withdraw ((account (eql *account-of-bank-president*)) amount)
(let ((overdraft (- amount (balance account))))
(when (plusp overdraft)
(incf (balance account) (embezzle *bank* overdraft)))
(call-next-method)))
Note, however, that the form in the EQL
specializer that provides the object to specialize on—*account-of-bank-president*
in this case—is evaluated once, when the DEFMETHOD
is evaluated. This method will be specialized on the value of *account-of-bank-president*
at the time the method is defined; changing the variable later won't change the method.
Outside the body of a method, CALL-NEXT-METHOD
has no meaning. Within a method, it's given a meaning by the generic function machinery that builds an
Conceptually, the effective method is built in three steps: First, the generic function builds a list of applicable methods based on the actual arguments it was passed. Second, the list of applicable methods is sorted according to the
To find applicable methods, the generic function compares the actual arguments with the corresponding parameter specializers in each of its methods. A method is applicable if, and only if, all the specializers are compatible with the corresponding arguments.
When the specializer is the name of a class, it's compatible if it names the actual class of the argument or one of its superclasses. (Recall that parameters without explicit specializers are implicitly specialized on the class T
so will be compatible with any argument.) An EQL
specializer is compatible only when the argument is the same object as was specified in the specializer.
Because
After the applicable methods have been found, the generic function machinery needs to sort them before it can combine them into an effective method. To order two applicable methods, the generic function compares their parameter specializers from left to right,[179] and the first specializer that's different between the two methods determines their ordering, with the method with the more specific specializer coming first.
Because only applicable methods are being sorted, you know all class specializers will name classes that the corresponding argument is actually an instance of. In the typical case, if two class specializers differ, one will be a subclass of the other. In that case, the specializer naming the subclass is considered more specific. This is why the method that specialized account
on checking-account
was considered more specific than the method that specialized it on bank-account
.
Multiple inheritance slightly complicates the notion of specificity since the actual argument may be an instance of two classes, neither of which is a subclass of the other. If such classes are used as parameter specializers, the generic function can't order them using only the rule that subclasses are more specific than their superclasses. In the next chapter I'll discuss how the notion of specificity is extended to deal with multiple inheritance. For now, suffice it to say that there's a deterministic algorithm for ordering class specializers.
Finally, an EQL
specializer is always more specific than any class specializer, and because only applicable methods are being considered, if more than one method has an EQL
specializer for a particular parameter, they must all have the same