Generic Functions and Methods
A generic function defines an abstract operation, specifying its name and a parameter list but no implementation. Here, for example, is how you might define a generic function, draw
, that will be used to draw different kinds of shapes on the screen:
(defgeneric draw (shape)
(:documentation "Draw the given shape on the screen."))
I’ll discuss the syntax of **DEFGENERIC**
in the next section; for now just note that this definition doesn’t contain any actual code.
A generic function is generic in the sense that it can—at least in theory—accept any objects as arguments.5 However, by itself a generic function can’t actually do anything; if you just define a generic function, no matter what arguments you call it with, it will signal an error. The actual implementation of a generic function is provided by methods. Each method provides an implementation of the generic function for particular classes of arguments. Perhaps the biggest difference between a generic function-based system and a message-passing system is that methods don’t belong to classes; they 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 specializing the required parameters defined by the generic function. For instance, on the generic function 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 applicable methods—those methods whose specializers are compatible with the actual arguments. If you invoke 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 effective method that handles the invocation.
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**
.