CLOS : Common Lisp Object System
Defining Classes
(defclass animal ()
((name :initarg :name :accessor animal-name :initform "unknown")
(sound :initarg :sound :accessor animal-sound)))
Creating Instances
(defvar *annie* (make-instance 'animal :name "Annie" :sound "Grunt"))
(animal-name *annie*) ; => "Annie"
(animal-sound *annie*) ; => "Grunt"
Inheritance
(defclass dog (animal)
((breed :initarg :breed :accessor dog-breed)))
(defvar *donnie* (make-instance 'dog :name "Donnie" :breed "Labrador"))
(animal-name *donnie*) ; => "Donnie" (inherited slot)
(dog-breed *donnie*) ; => "Labrador"
Generic Functions & Methods
(defgeneric speak (animal)
(:documentation "Make the animal speak"))
(defmethod speak ((a animal))
(format nil "~a says ~a!" (animal-name a) (animal-sound a)))
(defmethod speak ((d dog))
(format nil "The dog ~a barks loudly!" (animal-name d)))
(speak *annie*) ; => "Rex says Woof!"
(speak *donnie*) ; => "The dog Donnie barks loudly!"
Call-Next-Method
(defmethod speak ((d dog))
(concatenate 'string "[Dog specific] " (call-next-method)))
(speak *donnie*) ; => "[Dog specific] The dog Donnie barks loudly!"
Method Qualifiers (Before/After/Around)
(defmethod speak :before ((a animal))
(format t "--- About to speak ---~%"))
(defmethod speak :after ((a animal))
(format t "--- Done speaking ---~%"))
(defmethod speak :around ((a animal))
(format t "~%")
(call-next-method) ; runs before/primary/after chain
(format t "~%"))
Slot Access
(slot-value *annie* 'name) ; => "Annie"
(setf (animal-name *annie*) "Max") ; using accessor
(setf (slot-value *annie* 'name) "Max") ; direct slot access
Checking Types
(typep *annie* 'dog) ; => T
(typep *annie* 'animal) ; => T (inheritance)
(class-of *annie*) ; => #<STANDARD-CLASS DOG>
Multiple Inheritance
(defclass pet ()
((owner :initarg :owner :accessor pet-owner)))
(defclass pet-dog (dog pet) ()) ; inherits from both
(defvar *buddy* (make-instance 'pet-dog
:name "Buddy"
:owner "Alice"
:breed "Poodle"))
(pet-owner *buddy*) ; => "Alice"
(dog-breed *buddy*) ; => "Poodle"
Method Resolution Order (MRO) follows left-to-right depth-first, so pet-dog checks: pet-dog → dog → animal → pet.
Multiple Dispatch
CLOS dispatches on all arguments, not just the first (unlike most OO languages).
Basic Example
(defgeneric interact (a b)
(:documentation "Interaction between two entities"))
(defclass cat (animal) ())
(defclass dog (animal) ())
(defmethod interact ((d dog) (c cat))
(format t "~a chases ~a!~%" (animal-name d) (animal-name c)))
(defmethod interact ((c cat) (d dog))
(format t "~a hisses at ~a!~%" (animal-name c) (animal-name d)))
(defmethod interact ((d1 dog) (d2 dog))
(format t "~a and ~a play together!~%" (animal-name d1) (animal-name d2)))
(defvar *annie* (make-instance 'dog :name "Annie"))
(defvar *fido* (make-instance 'dog :name "Fido"))
(defvar *whiskers* (make-instance 'cat :name "Whiskers"))
(interact *annie* *whiskers*) ; => "Annie chases Whiskers!"
(interact *whiskers* *annie*) ; => "Whiskers hisses at Annie!"
(interact *annie* *fido*) ; => "Annie and Fido play together!"
Serialization Example
A more practical use case — dispatching on both object type and format:
(defclass json-format () ())
(defclass xml-format () ())
(defclass person ()
((name :initarg :name :accessor person-name)
(age :initarg :age :accessor person-age)))
(defclass product ()
((id :initarg :id :accessor product-id)
(price :initarg :price :accessor product-price)))
(defgeneric serialize (object format)
(:documentation "Serialize OBJECT to FORMAT"))
;; Person → JSON
(defmethod serialize ((p person) (f json-format))
(format nil "{\"name\": \"~a\", \"age\": ~a}"
(person-name p) (person-age p)))
;; Person → XML
(defmethod serialize ((p person) (f xml-format))
(format nil "<person><name>~a</name><age>~a</age></person>"
(person-name p) (person-age p)))
;; Product → JSON
(defmethod serialize ((p product) (f json-format))
(format nil "{\"id\": ~a, \"price\": ~a}"
(product-id p) (product-price p)))
;; Fallback
(defmethod serialize (object format)
(error "No serializer for ~a to ~a" (class-of object) (class-of format)))
(defvar *json* (make-instance 'json-format))
(defvar *xml* (make-instance 'xml-format))
(defvar *bob* (make-instance 'person :name "Bob" :age 30))
(defvar *prod* (make-instance 'product :id 42 :price 9.99))
(serialize *bob* *json*)
;; => "{\"name\": \"Bob\", \"age\": 30}"
(serialize *bob* *xml*)
;; => "<person><name>Bob</name><age>30</age></person>"
(serialize *prod* *json*)
;; => "{\"id\": 42, \"price\": 9.99}"
(serialize *prod* *xml*)
;; => Error: No serializer for PRODUCT to XML-FORMAT
Key Points
- Dispatch happens on the runtime type of all specialized arguments
- More specific methods take priority over less specific ones
- A method with no specializers acts as a fallback
- This replaces the need for
instanceofchains or visitor patterns found in other languages