LISP - Method Combinations
Lisp - Advanced OOP: Method Combination
Section titled “Lisp - Advanced OOP: Method Combination”The Common Lisp Object System (CLOS) is one of the most powerful object systems available. A key feature that sets it apart is method combination, which defines how CLOS combines the behavior of multiple applicable methods for a single generic function call. This allows for incredibly flexible and modular program design.
Background: Generic Functions and Methods
Section titled “Background: Generic Functions and Methods”First, a quick refresher:
- A generic function defines an interface for an operation (e.g.,
draw-shape). - A method provides a specific implementation of that generic function for certain argument types (e.g., a method for
(draw-shape ((s circle)))and another for(draw-shape ((s square)))).
Method combination answers the question: What happens when inheritance means multiple methods could apply to a single call?
The Standard Method Combination
Section titled “The Standard Method Combination”This is the default and most common type of method combination. It orchestrates four different kinds of methods, which run in a specific, layered order. We can use the analogy of preparing a meal to understand it:
- 1.
:aroundMethods: The Head Chef. Has ultimate control. It can run its own code, decide whether to proceed with the rest of the meal by using(call-next-method), and can even alter the final result. The most specific:aroundmethod runs first. - 2.
:beforeMethods: The Prep Work. These all run before the main action, in most-specific-first order (e.g., wash vegetables, then chop vegetables). They do not provide a return value. - 3. Primary Methods: The Main Course. Only the single most specific primary method runs. It defines the core behavior and provides the return value for the meal (unless an
:aroundmethod overrides it). - 4.
:afterMethods: The Cleanup. These all run after the main action, in most-specific-last order (e.g., clean the counter, then wash the dishes). They do not provide a return value.
Example: A Full Demonstration
Section titled “Example: A Full Demonstration”Let’s define a class hierarchy and a generic function with all four method types.
;; main.lisp(defclass vehicle () ())(defclass car (vehicle) ())
;; 1. Define the generic function(defgeneric perform-maintenance (v))
;; 2. Define the methods
;; :around method - has overall control(defmethod perform-maintenance :around ((v vehicle)) (format t "[AROUND] Starting maintenance check for ~a.~%" (class-name (class-of v))) (let ((report (call-next-method))) (format t "[AROUND] Maintenance complete. Report: ~a~%" report) (cons 'completed report)))
;; :before methods - run most-specific-first(defmethod perform-maintenance :before ((v vehicle)) (format t " [BEFORE] Checking tire pressure.~%"))(defmethod perform-maintenance :before ((v car)) (format t " [BEFORE] Checking engine oil level.~%"))
;; Primary methods - only the most specific one runs(defmethod perform-maintenance ((v vehicle)) (format t " [PRIMARY] Washing the vehicle.~%") '(washed))(defmethod perform-maintenance ((v car)) (format t " [PRIMARY] Rotating tires.~%") '(tires-rotated))
;; :after methods - run most-specific-last(defmethod perform-maintenance :after ((v vehicle)) (format t " [AFTER] Logging maintenance in vehicle logbook.~%"))(defmethod perform-maintenance :after ((v car)) (format t " [AFTER] Resetting 'Service Needed' light.~%"))
;; 3. Call the generic function(perform-maintenance (make-instance 'car))Output and Execution Flow
Section titled “Output and Execution Flow”; Execution Flow for a 'car' instance:; 1. The :around method starts.; 2. It calls next, triggering :before methods (car, then vehicle).; 3. The most specific primary method (car) runs and returns '(tires-rotated)'.; 4. :after methods run (vehicle, then car).; 5. Control returns to the :around method, which wraps the result.
[AROUND] Starting maintenance check for CAR. [BEFORE] Checking engine oil level. [BEFORE] Checking tire pressure. [PRIMARY] Rotating tires. [AFTER] Logging maintenance in vehicle logbook. [AFTER] Resetting 'Service Needed' light.[AROUND] Maintenance complete. Report: (TIRES-ROTATED); Return value -> (COMPLETED TIRES-ROTATED)Other Built-in Method Combinations
Section titled “Other Built-in Method Combinations”CLOS provides other ways to combine methods, where all applicable primary methods contribute to the result. You specify this in the defgeneric form.
+: The results of all applicable methods are added together.append: The list results of all methods are appended into a single list.and: If any method returnsnil, the combination stops and returnsnil. Otherwise, returns the value from the last method.list: Gathers the results of all methods into a list.
Example: The + Combination
Section titled “Example: The + Combination”(defclass item () ())(defclass taxable-item (item) ())(defclass shipping-item (item) ())
(defgeneric get-total-cost (i) (:method-combination + :most-specific-first))
(defmethod get-total-cost + ((i item)) 100) ; Base price(defmethod get-total-cost + ((i taxable-item)) 8) ; Tax(defmethod get-total-cost + ((i shipping-item)) 15) ; Shipping
;; An item that is both taxable and shippable(defclass book (taxable-item shipping-item) ())
(get-total-cost (make-instance 'book));=> 123 (100 + 8 + 15)Why Use Method Combination?
Section titled “Why Use Method Combination?”- Separation of Concerns: Keep core logic in primary methods, while handling peripheral tasks like logging, security, or caching in
:before,:after, or:aroundmethods. - Framework Extensibility: Allows users of a library to hook into and customize behavior without modifying the original source code.
- Modular Design: Build complex behaviors from small, reusable, and independent methods.