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.
What is a Generic Function?
Section titled “What is a Generic Function?”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.
Example: Defining a Generic Function
Section titled “Example: Defining a Generic Function”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))What is a Method?
Section titled “What is a Method?”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.
Example: Defining Methods
Section titled “Example: Defining Methods”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)))Key Concepts Explained
Section titled “Key Concepts Explained”- Dispatch: When you call a generic function like
(describe-entity some-object), CLOS performs method dispatch. It checks the class ofsome-objectand finds the most specificdefmethodthat 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:aroundmethods in addition to the primary method). This is an advanced topic for building highly extensible systems.
Complete, Runnable Project Example
Section titled “Complete, Runnable Project Example”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.
main.lisp
Section titled “main.lisp”;; --- 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).Expected Output
Section titled “Expected Output”When you load and run this file, the output will be:
[LOG] Describing a string: "Bilbo"[LOG] Describing a player named: AragornPractical Applications & Best Practices
Section titled “Practical Applications & Best Practices”- 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 likeplayer,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
defclasswith:initargfor constructor arguments and:accessor(or:reader/:writer) for slot access. This is the standard, idiomatic way to define classes in Common Lisp.