Skip to content

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).

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))

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.

;;; --- 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")

When you execute the code, the methods are combined and called in a specific order:

"--- Calling with an integer ---"
Integer-specific preparation for: 10
Preparing to process data: 10
--> Processing integer: 10, result: 20
"--- Calling with a string ---"
Preparing to process data: "test"
--> Processing string: "test", result: "test-processed"
  • When (process-data 10) is called:
    1. The most-specific :before method ((data integer)) runs first.
    1. The less-specific :before method ((data t)) runs next.
    1. Finally, the most-specific primary method ((data integer)) runs.
  • When (process-data "test") is called:
    1. Only one :before method is applicable ((data t)), so it runs.
    1. The most-specific primary method ((data string)) runs.
  • Common Error: Attempting to return a value from a :before method 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 :around method.
  • Best Practice: Keep :before methods 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.