LISP - Class Precedence
Lisp - Understanding Class Precedence in CLOS
Section titled “Lisp - Understanding Class Precedence in CLOS”The Common Lisp Object System (CLOS) features a powerful multiple inheritance system. When a class inherits from multiple parent classes (superclasses), a potential ambiguity arises: if multiple superclasses provide a method with the same name, which one should be used? Class Precedence resolves this ambiguity by defining a single, linear order of inheritance for every class.
Determining the Class Precedence List (CPL)
Section titled “Determining the Class Precedence List (CPL)”CLOS computes a Class Precedence List (CPL) for each class using a sophisticated algorithm (C3 Method Resolution Order). The CPL determines the complete inheritance hierarchy. The rules can be simplified as follows:
- A class always has higher precedence than its superclasses.
- The order of direct superclasses listed in the
defclassform is preserved. - The final list is a consistent, deterministic linearization of the entire class hierarchy.
- The built-in classes
standard-objectandtare always at the end of the list, providing a common root.
You can inspect the CPL of any class using the function (class-precedence-list (find-class 'class-name)). For a more readable output, we can map class-name over the result.
Example: Multiple Inheritance Order
Section titled “Example: Multiple Inheritance Order”Let’s see how the order of superclasses in defclass affects the CPL.
main.lisp
Section titled “main.lisp”;;; main.lisp - Demonstrating Class Precedence
(defclass a () ())(defclass b () ())
;; Class C inherits from A, then B(defclass c (a b) ())
;; Class D inherits from B, then A(defclass d (b a) ())
(defun show-cpl (class-symbol) "Prints the class precedence list for a given class symbol." (format t "CPL for ~a: ~a~%" class-symbol (mapcar #'class-name (ccl:class-precedence-list (find-class class-symbol)))))
(show-cpl 'a)(show-cpl 'b)(show-cpl 'c)(show-cpl 'd)Note: The function class-precedence-list is not exported by the ANSI standard. Most implementations provide it in a package like ccl, sb-mop, or clos. We use ccl:class-precedence-list here as an example.
Output
Section titled “Output”When you execute the code, the output clearly shows the linearized order:
CPL for A: (A STANDARD-OBJECT T)CPL for B: (B STANDARD-OBJECT T)CPL for C: (C A B STANDARD-OBJECT T)CPL for D: (D B A STANDARD-OBJECT T)Explanation
Section titled “Explanation”- For
c, since it’s defined as(defclass c (a b) ()), its CPL is(C A B ...). ClassAhas precedence overB. - For
d, defined as(defclass d (b a) ()), its CPL is(D B A ...). ClassBhas precedence overA.
Practical Impact: Method Dispatch
Section titled “Practical Impact: Method Dispatch”The primary purpose of the CPL is to determine which method to execute when a generic function is called. CLOS finds all applicable methods for the given arguments and executes the most specific one, as determined by the CPL.
Method Dispatch Example
Section titled “Method Dispatch Example”;; Define a generic function(defgeneric identify (obj))
;; Define methods for classes A and B(defmethod identify ((obj a)) "Method specialized for class A")
(defmethod identify ((obj b)) "Method specialized for class B")
;; Create instances of C and D(let ((instance-c (make-instance 'c)) (instance-d (make-instance 'd)))
(format t "Calling identify on an instance of C: ~s~%" (identify instance-c)) (format t "Calling identify on an instance of D: ~s~%" (identify instance-d)))Output
Section titled “Output”Calling identify on an instance of C: "Method specialized for class A"Calling identify on an instance of D: "Method specialized for class B"Explanation
Section titled “Explanation”- For
instance-c, bothidentifymethods (foraandb) are applicable. SinceAcomes beforeBinC’s CPL, the method forAis chosen as the most specific. - For
instance-d, the situation is reversed.Bcomes beforeAinD’s CPL, so the method forBis chosen.
Key Takeaways
Section titled “Key Takeaways”- Predictability: Class precedence provides a predictable and powerful mechanism for resolving method conflicts in complex inheritance hierarchies.
- Control: As a developer, you control the precedence by ordering the superclasses in
defclass. - Debugging: When unsure about method behavior, inspecting the
class-precedence-listis the first step in debugging. - Advanced Methods: This is the foundation for more advanced features like method combination (e.g.,
:before,:after, and:aroundmethods), which allow multiple methods to contribute to a single generic function call.