Previous | Table of Contents | Next |
CLOS decides by using the following rule: The applicable methods are ranked in order of precedence using the class precedence lists for each argument (in left-to-right order).
If the first parameter of one of the applicable methods is specialized on a more specific class than the first parameters of the other applicable methods, then it is the most specific method. If there are ties, then they are broken using the second argument, and so on.
The class precedence list of *vanilla* is vanilla ice-cream edible standard-object t, selecting methods 3 and 5 as the most applicable. The class precedence list of *sugar-sprinkles* is sugar-sprinkles sprinkles-mix-in edible standard-object t, selecting method 5 as more specific than method 3. The next most applicable method is method 3, followed by method 4, method 2, and then method 1.
defgeneric has a keyword option :argument-precedence-order, which can be used to change the default left-to-right order of the arguments CLOS uses to calculate the applicable methods by specifying the new order for the parameters in the argument list.Methods can also specialize on particular objects: For example, you can write the method:
(defmethod mix ((x (eql *vanilla*)) (y (eql *sugar-sprinkles*))) (format nil Vanilla ice-cream with sugar sprinkles is my favorite))
or
(defmethod mix ((x (eql cement)) (y (eql gravel))) concrete)
Specializations on individual objects always take precedence over the class specializations.
The parameter list for methods follows the Common Lisp specification for functions (with the restriction that only the required parameters can be specialized).
CLOS defines that all methods for a generic function must have congruent parameter lists. They must have the same number of required arguments and the same number of optional parameters (zero or more), all use &rest (or none), and all use &key (or none).
Methods can be augmented by auxiliary methods: before methods, after methods, and around methods.
Before methods allow custom code to run before the primary method. They are called, most specific first, before the rest of the method runs. As an example, before methods might be used to inform the rest of the system about the priority for a shared resource the rest of the method will use. After methods are called, most specific last, as the final part of a method call. As an example, after methods are often used to notify other parts of the system that the method has run. The rest of the method (between the before methods and the after methods) is what is normally considered the method and is called the primary method. The value of the primary method is returned by a call to the method, even though the after methods are called after the primary method.
Before methods and after methods permit custom behavior to be prepended and appended to a method. Around methods can also replace the primary method. Typically, an around method calls the primary method using call-next-method (this is why they are called around methods), but this is up to the around method implementer.
The procedure described, known as standard method combination, is the default method combination prescribed by CLOS. Other method combinations exist (as described in the next section).
As an example, use the class hierarchy defined in the previous section:
(defmethod mix ((ic ice-cream) (sprinkles sprinkles-mix-in)) (build-ice-cream-cone) (defmethod mix :before (x y) (take-order) (defmethod mix :before ((ic ice-cream) y) (pick-up-scoop)) (defmethod mix :after ((ic ice-cream) (sprinkles sprinkles-mix-in)) (add-sprinkles sprinkles ic) (defmethod mix :after ((ic ice-cream) y) (replace-scoop)
On calling
(mix (make-instance vanilla) (make-instance sugar-sprinkles))
which methods run? The effective method for this combination is obtained by calling in the following order:
The value returned is the value of the most specific primary method. The effective method for this combination looks like this:
(multiple-value-prog1 (progn (pick-up-scoop) ; most specific before-method (take-order) ; least specific before-method (build-ice-cream-cone) ; most specific primary method ) (replace-scoop) ; least specific after-method (add-sprinkles) ; most specific after method )
After methods dont have access to the value returned by the primary method. In many cases, you want to do some calculations, call the method, and then run another computation based on the value of the core method. As an example, you might only want to notify another part of the application that the method has run based on the return value of the core method. After methods wont let you do this.
An around method completely replaces the most specific method. For example, build-ice-cream-cone might fail if you are out of ice cream; otherwise, it returns the cone:
(defmethod mix :around ((ic ice-cream) (sprinkle sugar-sprinkles)) (let ((cone (call-next-method))) (when cone (take-payment)) cone))
Using call-next-method, this runs the next most specific method (i.e., build-ice-cream-cone)in this case, the entire framework defined before, including all the before methods, after methods, and the primary method. The usual convention, illustrated here, is to return the value of the next most specific method.
Previous | Table of Contents | Next |