LISP - :before Method Combination
Lisp - :before Method Combination
Section titled “Lisp - :before Method Combination”In the Common Lisp Object System (CLOS), method combination controls how different methods for a single generic function are combined and executed. The standard method combination provides several auxiliary method types, including :before methods. A :before method is a powerful tool for executing code before the primary method is called.
Common use cases for :before methods include:
- Input Validation: Checking arguments before the main logic runs.
- Setup: Ensuring preconditions or state are correctly established.
- Logging and Tracing: Printing a message or tracing a method call for debugging.
- Preliminary Calculations: Performing calculations whose results are stored for later use (e.g., in a slot).
Defining a :before Method
Section titled “Defining a :before Method”You define a :before method using defmethod with the :before qualifier between the generic function name and the parameter list. Crucially, the return values of :before methods are always ignored.
;; 1. Define a generic function.(defgeneric process-data (data))
;; 2. Define a :before method that applies to ALL types of data (specialized on T).(defmethod process-data :before ((data t)) (format t "[LOG] Preparing to process data: ~a~%" data))
;; 3. Define a more specific :before method just for integers.(defmethod process-data :before ((data integer)) (format t "[VALIDATE] Integer-specific check for ~a passed.~%" data))Execution Order and Example
Section titled “Execution Order and Example”When a generic function is called, CLOS first executes all applicable :before methods in most-specific-first order. After all :before methods have run, the single most-specific primary method is executed.
Complete Example
Section titled “Complete Example”;;; --- Generic Function Definition ---(defgeneric process-data (data))
;;; --- :before Methods (for setup and logging) ---;; Most-general :before method(defmethod process-data :before ((data t)) (format t "Preparing to process data: ~a~%" data))
;; More-specific :before method for integers(defmethod process-data :before ((data integer)) (format t "Integer-specific preparation for: ~a~%" data))
;;; --- Primary Methods (for the main logic) ---;; Primary method for integers(defmethod process-data ((data integer)) (format t "--> Processing integer: ~a, result: ~a~%" data (* data 2)))
;; Primary method for strings(defmethod process-data ((data string)) (format t "--> Processing string: ~a, result: ~a~%" data (concatenate 'string data "-processed")))
;;; --- Calling the Generic Function ---(print "--- Calling with an integer ---")(process-data 10)
(print "--- Calling with a string ---")(process-data "test")Output
Section titled “Output”When you execute the code, the methods are combined and called in a specific order:
"--- Calling with an integer ---"Integer-specific preparation for: 10Preparing to process data: 10--> Processing integer: 10, result: 20
"--- Calling with a string ---"Preparing to process data: "test"--> Processing string: "test", result: "test-processed"Explanation of Execution
Section titled “Explanation of Execution”- When
(process-data 10)is called: -
- The most-specific
:beforemethod ((data integer)) runs first.
- The most-specific
-
- The less-specific
:beforemethod ((data t)) runs next.
- The less-specific
-
- Finally, the most-specific primary method (
(data integer)) runs.
- Finally, the most-specific primary method (
- When
(process-data "test")is called: -
- Only one
:beforemethod is applicable ((data t)), so it runs.
- Only one
-
- The most-specific primary method (
(data string)) runs.
- The most-specific primary method (
Common Pitfalls and Best Practices
Section titled “Common Pitfalls and Best Practices”- Common Error: Attempting to return a value from a
:beforemethod to stop execution or change the primary method’s arguments. This will not work. Their return values are discarded. For that level of control, you must use an:aroundmethod. - Best Practice: Keep
:beforemethods simple and focused on a single task like validation or logging. They should not contain complex application logic. They are for preparing the ground for the primary method, not for doing its work.