Previous Table of Contents Next


Here’s how you linearize the class and its superclasses: Lay out the inheritance diagram with superclasses for each class above the class, but in the same left-to-right order that they appear in the definition of defclass. Now perform the following graph traversal:

1.  Start at the bottom of the class graph.
2.  Move upward, always taking the leftmost untraversed branch.
3.  If a class node has a second branch, enter it from the right. If you can’t center from the right, retrace until you get to a class node with an untraversed path leading upward. Move upward again, and repeat the preceding steps.
4.  At the top of the graph, the node t, stop.

The order of the classes in the graph traversal is the class precedence list.

The precedence list for cookies-n-cream in the preceding example is cookies-n-cream vanilla ice-cream oreos basic-mix-in edible standard-object t. cookies-n-cream inherits the fat-content value from superclass vanilla. If vanilla did not have a fat-content slot, then cookies-n-cream would inherit the fat-content value from oreos, and if neither vanilla nor oreos had the fat-content slot, cookies-n-cream would inherit the fat-content slot from edible.

5.4.4.1. Inheritance Behavior for Slots

Different slot options have different inheritance characteristics. Slot options can be overridden in subclasses. For the purpose of inheritance, the precedence of a slot specifier is determined by the precedence of the class that specifies it. The rules for inheritance are as follows:

:accessor, :reader and :writer specify methods that are inherited according to the way methods are inherited.
:allocation is inherited from the most specific class that provides a slot specifier, whether or not :allocation is specified.
:documentation is controlled by the most specific slot specifier that provides the :documentation option to the slot.
Any initarg in the class precedence list for the same slot can be used to initialize that slot.
:initform is controlled by the most specific slot specifier that provides the :initform option to the slot.
:type must satisfy all types specified in the class precedence list, so an inherited type can be more stringent than the types in its superclasses, but cannot be less stringent.

5.4.5. Methods and Generic Functions

A generic function is made up of one or more methods. You’ve already seen examples of generic functions and methods in section 5.3.3. There are two types of methods: primary methods and auxiliary methods. Primary methods do the main work of computation and return the value specified by the interface. Auxiliary methods can perform additional computation that is convenient to represent as a modular piece. You have already seen the use of an auxiliary method in adding a notification to the draw method in section 5.3.6. Auxiliary methods included are defined as before methods, around methods, or after methods, and are discussed further later.

Methods that apply to a class must have arguments specialized on the class or its superclasses. Arguments that are not specialized explicitly are specialized on class t.

In the basic ice cream example, suppose you want a method that actually adds sprinkles. The method may be more general than just adding sprinkles: You might like to add toppings as well and you might like to use it for edibles besides ice cream, such as low-fat frozen yogurt or fruit-ice. Therefore, you can add a generic function:

   (defgeneric mix (x y)

You could have a method that doesn’t specialize at all:

   (defmethod mix (x y)
      ;; method 1
      (error “Don’t know how to mix ∼a and ∼a” x y) )

The arguments are not specialized and this is equivalent to specializing the argument to t, the root class of everything. The definition

    (defmethod mix ((x t) (y t))
       ;; method 1
       (error “Don’t know how to mix ∼a and ∼a” x y) )

is exactly equivalent to the first definition.

A common technique is to define default methods that the system can always fall back on:

    (defmethod mix ((x edible) (y edible))
       ;; method 2
       (format nil “Food ∼a mixed with ∼a”
             (type x) (type y)))

Specialize the method for ice-cream:

    (defmethod mix ((x ice-cream) (y edible))
       ;; method 3
       (format nil “Ice-cream ∼a with ∼a”
             (type x) (type y)))

This specializes on the first argument only. Now specialize on the second argument:

    (defmethod mix ((x edible) (y sprinkles-mix-in))
       ;; method 4
       (format nil “Food ∼a with ∼a sprinkles on top”
             (type x) (type y)))

Now specialize on both arguments using ice-cream and sprinkles-mix-in:

    (defmethod mix ((x ice-cream) (y sprinkles-mix-in))
       ;; method 5
       (format nil “Ice-cream ∼a sprinkles on top”
             (type x) (type y)))

CLOS determines which of these methods to call in any given situation by calculating the applicable methods. A method is applicable if each of the arguments matches the specialized parameters of the possible method definitions. Calling the following:

   (setf *vanilla* (make-instance ‘vanilla))
   (setf *sugar-sprinkles* (make-instance ‘sugar-sprinkles))

   (mix *vanilla* *sugar-sprinkles*)

would result in every one of the preceding five methods being applicable. For example, method 2 is applicable because *vanilla* is of type ice-cream and *sugar-sprinkles* is of type edible. Method 5 is applicable because *vanilla* is of type ice-cream and *sugar-sprinkles* is also of type sugar-sprinkles. CLOS ranks these methods into an order from most specific to least specific and calls the most specific (in this case, method 5), which is what you would expect.

Redefining method 5 as follows:

   (defmethod mix ((x ice-cream) (y sprinkles-mix-in))
      ;; method 5
      (call-next-method))

would result in the next most specific method being called. There are two obvious candidates: method 3 (specializing on ice-cream and edible) and method 4 (specializing on edible and sprinkles-mix-in). In fact, CLOS chooses method 3.


Previous Table of Contents Next