Skip to content

LISP - Generic Functions

Modern Lisp: Generic Functions and Methods (CLOS)

Section titled “Modern Lisp: Generic Functions and Methods (CLOS)”

The Common Lisp Object System (CLOS) offers a highly flexible and powerful object-oriented programming paradigm. A cornerstone of CLOS is the concept of generic functions, which allow you to define operations that can be implemented differently for various data types.

A generic function, defined with defgeneric, establishes a name and a parameter list for an operation, but it doesn’t contain any implementation logic itself. Think of it as an interface or a protocol that other specialized functions, called methods, will implement.

Here, we define a generic function describe-entity that takes one argument. This declaration signals that there will be an operation named describe-entity, but how it works will depend on the type of entity it receives.

;; Defines the interface for our function.
;; It specifies the name `describe-entity` and its parameter list `(entity)`.
(defgeneric describe-entity (entity))

A method, defined with defmethod, provides the actual implementation for a generic function for specific argument types. The types you specify in the method’s parameter list are called specializers. When you call a generic function, CLOS examines the types of the arguments and dispatches to the most specific matching method.

Let’s define a simple player class and then create two methods for our describe-entity generic function. One will handle player objects, and the other will handle simple strings.

;; First, define a simple class to work with.
(defclass player ()
((name :initarg :name
:accessor player-name
:documentation "The player's name.")))
;; Define a method specialized for STRING arguments.
(defmethod describe-entity ((entity string))
(format t "Entity is a simple string: ~s~%" entity))
;; Define a method specialized for PLAYER objects.
(defmethod describe-entity ((entity player))
(format t "Entity is a player named: ~a~%" (player-name entity)))
  • Dispatch: When you call a generic function like (describe-entity some-object), CLOS performs method dispatch. It checks the class of some-object and finds the most specific defmethod that matches. This is dynamic dispatch because it happens at runtime.
  • Multimethods: Lisp’s dispatch is not limited to just the first argument. You can specialize on multiple arguments, making it a powerful system of multimethods. For example: (defmethod interact ((p1 player) (p2 monster))).
  • Specializers: A specializer is the part of a method’s parameter list that specifies a type, like (entity string) or (entity player). It tells the dispatch system which method to choose.
  • Method Combination: CLOS allows you to combine multiple applicable methods in sophisticated ways (e.g., running :before, :after, or :around methods in addition to the primary method). This is an advanced topic for building highly extensible systems.

Here is a complete, well-formatted, and runnable example that ties everything together. You can save this as a file (e.g., main.lisp) and run it with a Common Lisp implementation like SBCL.

;; --- Class Definition ---
;; A modern class definition using initargs and accessors.
(defclass player ()
((name :initarg :name
:accessor player-name
:documentation "The player's name.")))
;; --- Generic Function Definition ---
;; Defines the function's signature.
(defgeneric describe-entity (entity))
;; --- Method Definitions ---
;; Method specialized for STRING arguments.
(defmethod describe-entity ((entity string))
(format t "[LOG] Describing a string: ~s~%" entity))
;; Method specialized for PLAYER objects.
(defmethod describe-entity ((entity player))
(format t "[LOG] Describing a player named: ~a~%" (player-name entity)))
;; --- Main Execution Logic ---
(defun main ()
;; Create an instance of the PLAYER class.
(let ((hero (make-instance 'player :name "Aragorn"))
(npc-name "Bilbo"))
;; Call the generic function with a STRING.
;; CLOS dispatches to the string-specialized method.
(describe-entity npc-name)
;; Call the generic function with a PLAYER object.
;; CLOS dispatches to the player-specialized method.
(describe-entity hero)))
;; To run the code:
(main)
;; --- Common Errors & Debugging ---
;; What happens if you call it with a type that has no method?
;; (describe-entity 123)
;; This would signal a `NO-APPLICABLE-METHOD` error, telling you
;; that Lisp couldn't find a method for the type `FIXNUM` (an integer).

When you load and run this file, the output will be:

[LOG] Describing a string: "Bilbo"
[LOG] Describing a player named: Aragorn
  • Extensible Systems: Use generic functions to allow users of your library to add new behaviors for their own custom classes without modifying your source code.
  • Game Development: A (draw object) generic function could have methods for drawing different game entities like player, enemy, item, etc.
  • Data Processing: An (encode data) generic function can be specialized to handle encoding for different data formats (e.g., JSON, XML, binary).
  • Best Practice: Always use defclass with :initarg for constructor arguments and :accessor (or :reader/:writer) for slot access. This is the standard, idiomatic way to define classes in Common Lisp.