Skip to content

LISP - Class

Modern Object-Oriented Programming in Common Lisp: The Common Lisp Object System (CLOS)

Section titled “Modern Object-Oriented Programming in Common Lisp: The Common Lisp Object System (CLOS)”

While Common Lisp predates the mainstream adoption of object-oriented programming (OOP), it features one of the most powerful and flexible object systems ever designed: the Common Lisp Object System (CLOS). This chapter introduces how to define classes, create objects, and work with methods in a modern Lisp style.

The defclass macro is the cornerstone of CLOS. It defines a new class, which serves as a template for creating objects. Its general syntax is:

(defclass class-name (superclass-name*)
(slot-description*)
class-option*)
  • class-name: The name of your new class.
  • superclass-name*: A list of parent classes from which this class will inherit. This enables multiple inheritance.
  • slot-description*: A list of slots (member variables or fields) that will store data for instances of this class.

Each slot is defined with a name and optional slot-options that control its behavior. The most important options are:

  • :accessor function-name: Creates a function that can both read and write the slot’s value.
  • :reader function-name: Creates a function that can only read the slot’s value.
  • :writer function-name: Creates a function that can only write the slot’s value.
  • :initarg :keyword-name: Specifies a keyword that can be used with make-instance to initialize the slot’s value.
  • :initform expression: Provides a default value for the slot if it’s not otherwise initialized.

Let’s define a Box class with length, breadth, and height, using modern best practices for access and initialization.

;; A well-defined Box class with accessors, initial arguments, and default values.
(defclass box ()
((length :accessor box-length
:initarg :length
:initform 1)
(breadth :accessor box-breadth
:initarg :breadth
:initform 1)
(height :accessor box-height
:initarg :height
:initform 1)))

The generic function make-instance is used to create and return a new object (an instance) of a class. The best practice is to use the :initarg keywords defined in defclass to provide initial values.

(make-instance class-name &key initarg1 value1 initarg2 value2 ...)

This example demonstrates creating a box instance and using its accessor functions to interact with its slots. Save this code as main.lisp.

;; Define the Box class (as above)
(defclass box ()
((length :accessor box-length
:initarg :length
:initform 1)
(breadth :accessor box-breadth
:initarg :breadth
:initform 1)
(height :accessor box-height
:initarg :height
:initform 1)))
;; Create an instance of the 'box' class using initargs.
;; This is the modern, preferred way.
(defparameter *my-box* (make-instance 'box :length 10 :breadth 20 :height 5))
;; Access and print the slot values using the accessor functions.
(format t "Length of the Box is ~d~%" (box-length *my-box*))
(format t "Breadth of the Box is ~d~%" (box-breadth *my-box*))
(format t "Height of the Box is ~d~%" (box-height *my-box*))
;; We can also modify a slot's value using the accessor with `setf`.
(setf (box-height *my-box*) 7)
(format t "New height of the Box is ~d~%" (box-height *my-box*))
Length of the Box is 10
Breadth of the Box is 20
Height of the Box is 5
New height of the Box is 7

In CLOS, behavior is defined through generic functions and methods. A generic function defines the name and argument signature, while methods provide the actual implementation, specialized for different classes of arguments. The defmethod macro defines a method.

Let’s add a volume method. Instead of adding a volume slot, which can become stale if dimensions change, we’ll compute it on demand. This is a more robust design.

;; The Box class (no changes needed)
(defclass box ()
((length :accessor box-length :initarg :length :initform 1)
(breadth :accessor box-breadth :initarg :breadth :initform 1)
(height :accessor box-height :initarg :height :initform 1)))
;; Define a method `volume` specialized for an object of class `box`.
(defmethod volume ((b box))
"Calculates the volume of a box."
;; `with-slots` provides a clean way to access slots by name
;; without repeatedly calling accessor functions.
(with-slots (length breadth height) b
(* length breadth height)))
;; Create an instance
(defparameter *my-box* (make-instance 'box :length 10 :breadth 10 :height 5))
;; Display the dimensions and the calculated volume
(format t "Length of the Box is ~d~%" (box-length *my-box*))
(format t "Breadth of the Box is ~d~%" (box-breadth *my-box*))
(format t "Height of the Box is ~d~%" (box-height *my-box*))
(format t "Volume of the Box is ~d~%" (volume *my-box*))
Length of the Box is 10
Breadth of the Box is 10
Height of the Box is 5
Volume of the Box is 500

Inheritance allows a new class (subclass) to be based on an existing class (superclass), inheriting its slots and methods. The subclass can add new slots and methods or specialize existing ones.

Let’s create a weighted-box that inherits from box and adds a weight slot and a density method.

;; Base class
(defclass box ()
((length :accessor box-length :initarg :length :initform 1)
(breadth :accessor box-breadth :initarg :breadth :initform 1)
(height :accessor box-height :initarg :height :initform 1)))
(defmethod volume ((b box))
(with-slots (length breadth height) b
(* length breadth height)))
;; Derived class 'weighted-box' inherits from 'box'
(defclass weighted-box (box)
((weight :accessor box-weight
:initarg :weight
:initform 0)))
;; New method specialized for 'weighted-box'
(defmethod density ((wb weighted-box))
"Calculates the density of a weighted-box."
(when (plusp (volume wb))
(/ (box-weight wb) (volume wb))))
;; Create an instance of the derived class
(defparameter *my-weighted-box*
(make-instance 'weighted-box :length 10 :breadth 10 :height 5 :weight 2500))
;; Displaying values
(format t "Length of the Weighted Box is ~d~%" (box-length *my-weighted-box*))
(format t "Volume of the Weighted Box is ~d~%" (volume *my-weighted-box*)) ; Inherited method
(format t "Weight of the Weighted Box is ~d~%" (box-weight *my-weighted-box*))
(format t "Density of the Weighted Box is ~f~%" (density *my-weighted-box*))
Length of the Weighted Box is 10
Volume of the Weighted Box is 500
Weight of the Weighted Box is 2500
Density of the Weighted Box is 5.0