LISP - :after Method Combination
Lisp - :after Method Combination
Section titled “Lisp - :after Method Combination”The Common Lisp Object System (CLOS) offers a powerful feature called method combination, which controls how different methods for a generic function are combined and executed. The standard method combination uses qualifiers like :after to define auxiliary methods that run in addition to the primary method. Think of them as ‘hooks’ or ‘middleware’ that let you reliably execute code after the main logic.
The :after method qualifier is particularly useful for:
- Resource Cleanup: Ensuring files or network connections are closed after use.
- Logging and Auditing: Recording that an action was completed.
- State Updates: Refreshing a cache or updating a UI after data has changed.
Defining an :after Method
Section titled “Defining an :after Method”You define an :after method using defmethod with the :after qualifier placed between the generic function name and the parameter list. The return values of :after methods are always ignored.
;; 1. Define a generic function.(defgeneric process-data (data))
;; 2. Define an :after method.;; This method will run *after* any applicable primary method for `process-data`.(defmethod process-data :after ((data t)) (format t "[LOG] Finished processing data: ~a~%" data))
;; 3. Define another, more specific :after method.(defmethod process-data :after ((data integer)) (format t "[CLEANUP] Integer-specific cleanup complete.~%"))Execution Order
Section titled “Execution Order”When a generic function is called, CLOS runs the applicable methods in a specific order:
- The most specific
:aroundmethod (if any). - All applicable
:beforemethods, from most-specific to least-specific. - The most-specific primary method only.
- All applicable
:aftermethods, from least-specific to most-specific.
Practical Example: :after in Action
Section titled “Practical Example: :after in Action”Let’s see a complete example demonstrating the call order and use case.
main.lisp
Section titled “main.lisp”;; 1. Generic function definition(defgeneric process-data (data))
;; 2. Auxiliary :after methods
;; This is the LEAST specific :after method (applies to all types `t`)(defmethod process-data :after ((data t)) (format t "--> :after method for T (least specific) called.~%"))
;; This is a MORE specific :after method (applies only to integers)(defmethod process-data :after ((data integer)) (format t "--> :after method for INTEGER (most specific) called.~%"))
;; 3. Primary methods (the main logic)
;; Primary method for integers(defmethod process-data ((data integer)) (format t "Running PRIMARY method for INTEGER.~%") (* data 2)) ; The return value of the primary method is the return value of the whole call.
;; Primary method for strings(defmethod process-data ((data string)) (format t "Running PRIMARY method for STRING.~%") (concatenate 'string data "-processed"))
;; --- Function Calls ---(format t "--- Calling (process-data 10) ---~%")(process-data 10)
(format t "~%--- Calling (process-data \"test\") ---~%")(process-data "test")Output
Section titled “Output”When you execute the code, you’ll see the precise order of execution:
--- Calling (process-data 10) ---Running PRIMARY method for INTEGER.--> :after method for T (least specific) called.--> :after method for INTEGER (most specific) called.
--- Calling (process-data "test") ---Running PRIMARY method for STRING.--> :after method for T (least specific) called.Explanation
Section titled “Explanation”- When
(process-data 10)is called: The primary method forintegerruns first. Then, the:aftermethods are executed, starting with the least specific (t) and ending with the most specific (integer). - When
(process-data "test")is called: The primary method forstringruns. Afterward, only the:aftermethod fortis applicable, so it runs. - Return Values: Notice that the return values from the
:aftermethods are discarded. The final return value of the generic function call is always the value returned by the primary method.
Debugging Tip
Section titled “Debugging Tip”If you are unsure which methods apply to a generic function, you can use describe at the REPL:
;; In your REPL, after defining the methods:CL-USER> (describe 'process-data)
;; The output will list all defined methods and their qualifiers.