The Common Lisp Object System (CLOS) is the object-oriented programming subsystem of the Common Lisp language. It is a powerful, dynamic object system that integrates classes, generic functions, multiple dispatch, and a meta-object protocol (MOP). CLOS is notable for its flexibility, allowing runtime class redefinition, method combination, and first-class meta-objects, making it a foundational tool for advanced Lisp programming and domain-specific language design.
1 History and Motivation
1.1 Origins in Flavors and New Flavors
The earliest object-oriented extension to Lisp was Flavors, developed at the MIT AI Lab in the late 1970s. Flavors introduced message passing, multiple inheritance, and mixin classes. The name derived from the "flavor" of a symbol. Subsequent work at Symbolics produced New Flavors, which moved toward generic functions and method combination, foreshadowing CLOS.
1.2 Development by the Common Lisp community
In the early 1980s, the Common Lisp community sought a unified object system. The Lisp Machine vendors (Symbolics, Lisp Machines Incorporated, Xerox) had incompatible object systems. Under the aegis of the X3J13 ANSI committee, a team led by Daniel G. Bobrow, Linda G. DeMichiel, Richard P. Gabriel, and others designed CLOS, drawing from Flavors, CommonLoops (Xerox PARC), and other experimental systems.
1.3 Standardization in ANSI Common Lisp
CLOS was adopted as part of the ANSI Common Lisp standard (ANSI X3.226-1994). The specification includes both the programmer-visible features (classes, generic functions, methods) and the Meta-Object Protocol (MOP), though the MOP is defined in a separate but commonly referenced document (the “CLOS MOP specification”).
2 Core Concepts
2.1 Classes
2.1.1 Class definition (defclass)
Classes are defined with defclass:
(defclass point ()
((x :initarg :x :accessor point-x)
(y :initarg :y :accessor point-y)))
The first argument is the class name, followed by a list of superclasses (here empty means inheriting from standard-object), then a list of slot specifiers.
2.1.2 Slots and slot options
Each slot can have options including :initarg (keyword argument to make-instance), :initform (default value), :reader, :writer, :accessor, :type, :allocation (:instance or :class), and :documentation. Slots may also have :metaclass options when the MOP is used.
2.1.3 Inheritance and class precedence list
CLOS supports multiple inheritance. The class precedence list (CPL) is a total order of all superclasses computed using a topological sort algorithm (C3 linearization, as specified in ANSI CL). The CPL determines method lookup and slot inheritance.
2.2 Instances
2.2.1 Instance creation (make-instance)
Instances are created with make-instance, which calls initialize-instance under the standard initialization protocol:
(make-instance 'point :x 3 :y 4)
2.2.2 Slot access (slot-value, with-slots, with-accessors)
Direct slot access is via slot-value. Macros with-slots and with-accessors provide lexical bindings:
(with-slots (x y) point-instance
(format t "~a ~a" x y))
with-accessors uses accessor functions instead of direct slot-value.
2.2.3 Initialization protocol (initialize-instance, shared-initialize)
The initialization chain involves initialize-instance (primary method) and shared-initialize. Users can specialize initialize-instance to add custom initialization logic, often calling call-next-method.
2.3 Generic Functions and Methods
2.3.1 Generic function definition (defgeneric)
A generic function declares a family of methods sharing a name and argument list:
(defgeneric area (shape))
It can specify :argument-precedence-order, :documentation, and method combination type.
2.3.2 Method definition (defmethod)
Methods are defined independently of the generic function:
(defmethod area ((c circle))
(* pi (expt (circle-radius c) 2)))
2.3.3 Multiple dispatch and method dispatch order
CLOS supports multiple dispatch: methods specialize on any required argument. Dispatch order first selects the most specific matching method according to the class precedence lists of the arguments.
2.3.4 Specializers (class, eql, and other)
Specializers are class (by default) or eql specializers. eql specializers match a specific object (e.g., (defmethod foo ((x eql 42)) ...)). There is also keyword specializer (a form of eql) and, via the MOP, user-defined specializers.
3 Method Combination
3.1 Standard method combination
3.1.1 Primary, :before, :after, :around methods
Standard method combination orders methods into a chain: all :around methods are invoked first (outermost to innermost); within them, :before methods (most specific first), then the most specific primary method, then :after methods (most specific last). :around methods must call call-next-method to continue.
3.1.2 Call-next-method and next-method-p
call-next-method invokes the next method in the effective method. Without arguments, it passes the original arguments; explicit arguments may be provided (avoid unless carefully considered). next-method-p tests if a next method exists.
3.2 Custom method combination (define-method-combination)
Programmers can define custom method combinations using define-method-combination. This allows, for example, and, or, progn, append, list, max, min combinations, or completely arbitrary patterns. The combination specifies how methods of different roles are grouped and composed.
4 Meta-Object Protocol (MOP)
4.1 Roles of meta-objects (classes, generic functions, methods)
Meta-objects are first-class objects that represent classes, generic functions, and methods. They can be inspected and manipulated at runtime. The MOP defines the protocols that govern class creation, slot access, method dispatch, and instance creation.
4.2 Key meta-classes (standard-class, funcallable-standard-class)
The two main meta-classes are standard-class (for standard instances) and funcallable-standard-class (for instances that can be called like functions). Users can define their own subclasses of these meta-classes to alter class behavior.
4.3 Customizing class behavior (defclass with :metaclass)
A defclass can specify a :metaclass other than standard-class:
(defclass tracked-class ()
()
(:metaclass my-meta-class))
This overrides how instances, slots, and inheritance are handled.
4.4 MOP functions (class-prototype, compute-slots, validate-superclass)
Key MOP functions include:
class-prototype: returns a prototype instance (used internally).compute-slots: determines the effective slot descriptors for a class.validate-superclass: controls which superclasses are acceptable under a given meta-class.
The MOP also provides compute-effective-method, compute-discriminating-function, and other functions for complete customization.
5 Advanced Features
5.1 Reinitialization and class redefinition
CLOS allows changing class definitions at runtime. When a class is redefined, existing instances are automatically updated.
5.1.1 update-instance-for-redefined-class
This generic function is called when a class is redefined. It can be specialized to copy, transform, or discard slots from old instances. The default behavior adds new slots (initialized to nil or :initform) and removes slots that no longer exist.
5.1.2 update-instance-for-different-class (change-class)
change-class transforms an instance of one class into an instance of another class, calling update-instance-for-different-class to handle slot mappings.
5.2 Object equivalence and identity
CLOS instances are compared by identity via eq by default. equal may descend into structure for arrays and strings but not for arbitrary CLOS instances. Users can define their own equivalence predicates (e.g., via :equalp or methods on =, eql specializers).
5.3 Slot-value using (setf) and accessor functions
All slot access forms support setf for mutation. Accessors defined with :accessor generate both reader and writer generic functions. Direct use of (setf slot-value) is also permissible.
6 Example Applications and Code Patterns
6.1 Defining a simple class hierarchy
(defclass animal ()
((name :initarg :name :reader name)))
(defclass mammal (animal) ())
(defclass bird (animal) ())
(defmethod speak ((a animal))
"...")
(defmethod speak ((m mammal))
"Some mammal sound")
(defmethod speak ((b bird))
"Chirp")
6.2 Using multiple dispatch for algebraic operations
(defgeneric add (a b))
(defmethod add ((x number) (y number)) (+ x y))
(defmethod add ((x string) (y string)) (concatenate 'string x y))
(defmethod add ((x vector) (y vector)) (map 'vector #'+ x y))
6.3 Implementing a DSL with CLOS
CLOS classes can represent abstract syntax tree nodes. Methods serve as semantic actions. The MOP can be used to generate boilerplate code for symbol processing, type checking, or compilation passes.
7 Comparison with Other Object Systems
7.1 CLOS vs. Smalltalk
Smalltalk uses class-based single dispatch with message passing and dynamic typing. CLOS provides multiple dispatch, method combination, and a fully reifiable MOP. Smalltalk’s methods are owned by classes; CLOS disconnects methods from classes, giving greater flexibility.
7.2 CLOS vs. C++ and Java
C++ and Java use compile-time class hierarchies, single dispatch (virtual functions), and fixed class definitions. CLOS supports runtime class redefinition, multiple dispatch, and method combination. CLOS is more dynamic but less efficient; C++/Java emphasize performance and static type safety.
7.3 CLOS vs. Python and Ruby
Python and Ruby have dynamic object systems with single dispatch, duck typing, and open classes. CLOS offers multiple dispatch, a formal MOP, and method combinations. Python’s metaclasses are similar in spirit to CLOS meta-objects but are more restricted. Ruby’s open classes allow runtime changes but lack the disciplined protocols of CLOS.