necessarily run all the primary methods because of those macros' short-circuiting behavior—a generic function using the AND combination will return NIL as soon as one of the methods does and will return the value of the last method otherwise. Similarly, the OR combination will return the first non- NIL value returned by any of the methods.

To define a generic function that uses a particular method combination, you include a :method- combination option in the DEFGENERIC form. The value supplied with this option is the name of the method combination you want to use. For example, to define a generic function, priority, that returns the sum of values returned by individual methods using the + method combination, you might write this:

(defgeneric priority (job)

(:documentation 'Return the priority at which the job should be run.')

(:method-combination +))

By default all these method combinations combine the primary methods in most-specific-first order. However, you can reverse the order by including the keyword :most-specific-last after the name of the method combination in the DEFGENERIC form. The order probably doesn't matter if you're using the + combination unless the methods have side effects, but for demonstration purposes you can change priority to use most-specific-last order like this:

(defgeneric priority (job)

(:documentation 'Return the priority at which the job should be run.')

(:method-combination + :most-specific-last))

The primary methods on a generic function that uses one of these combinations must be qualified with the name of the method combination. Thus, a primary method defined on priority might look like this:

(defmethod priority + ((job express-job)) 10)

This makes it obvious when you see a method definition that it's part of a particular kind of generic function.

All the simple built-in method combinations also support :around methods that work like :around methods in the standard method combination: the most specific :around method runs before any other methods, and CALL-NEXT-METHOD is used to pass control to less-and-less-specific :around methods until it reaches the combined primary methods. The :most-specific-last option doesn't affect the order of :around methods. And, as I mentioned before, the built-in method combinations don't support :before or :after methods.

Like the standard method combination, these method combinations don't allow you to do anything you couldn't do 'by hand.' Rather, they allow you to express what you want and let the language take care of wiring everything together for you, making your code both more concise and more expressive.

That said, probably 99 percent of the time, the standard method combination will be exactly what you want. Of the remaining 1 percent, probably 99 percent of them will be handled by one of the simple built-in method combinations. If you run into one of the 1 percent of 1 percent of cases where none of the built-in combinations suffices, you can look up DEFINE-METHOD-COMBINATION in your favorite Common Lisp reference.

Multimethods

Methods that explicitly specialize more than one of the generic function's required parameters are called multimethods. Multimethods are where generic functions and message passing really part ways. Multimethods don't fit into message-passing languages because they don't belong to a particular class; instead, each multimethod defines a part of the implementations of a given generic function that applies when the generic function is invoked with arguments that match all the method's specialized parameters.

Multimethods vs. Method Overloading

Programmers used to statically typed message-passing languages such as Java and C++ may think multimethods sound similar to a feature of those languages called method overloading. However, these two language features are actually quite different since overloaded methods are chosen at compile time, based on the compile-time type of the arguments, not at runtime. To see how this works, consider the following two Java classes:

public class A {

public void foo(A a) { System.out.println('A/A'); }

public void foo(B b) { System.out.println('A/B'); }

}

public class B extends A {

public void foo(A a) { System.out.println('B/A'); }

public void foo(B b) { System.out.println('B/B'); }

}

Now consider what happens when you run the main method from this class.

public class Main {

public static void main(String[] argv) {

A obj = argv[0].equals('A') ? new A() : new B();

obj.foo(obj);

}

}

When you tell Main to instantiate an A, it prints 'A/A' as you'd probably expect.

bash$ java com.gigamonkeys.Main A

A/A

However, if you tell Main to instantiate a B, then the true type of obj is taken into account for only half the dispatching.

bash$ java com.gigamonkeys.Main B

B/A

If overloaded methods worked like Common Lisp's multimethods, then that would print 'B/B' instead. It is possible to implement multiple dispatch by hand in message-passing languages, but this runs against the grain of the message-passing model since the code in a multiply dispatched method doesn't belong to any one class.

Multimethods are perfect for all those situations where, in a message-passing language, you struggle to decide to which class a certain behavior ought to belong. Is the sound a drum makes when it's hit with a drumstick a function of what kind of drum it is or what kind of stick you use to hit it? Both, of course. To model this situation in Common Lisp, you simply define a generic function beat that takes two arguments.

(defgeneric beat (drum stick)

(:documentation

'Produce a sound by hitting the given drum with the given stick.'))

Then you can define various multimethods to implement beat for the combinations you care about. For example:

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату