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.
Defining Classes with defclass
Section titled “Defining Classes with defclass”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 withmake-instanceto 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)))Creating Instances of a Class
Section titled “Creating Instances of a Class”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 ...)Example: Creating and Inspecting a Box
Section titled “Example: Creating and Inspecting a Box”This example demonstrates creating a box instance and using its accessor functions to interact with its slots. Save this code as main.lisp.
main.lisp
Section titled “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*))Output
Section titled “Output”Length of the Box is 10Breadth of the Box is 20Height of the Box is 5New height of the Box is 7Defining Generic Functions and Methods
Section titled “Defining Generic Functions and Methods”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.
main.lisp (Updated)
Section titled “main.lisp (Updated)”;; 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*))Output
Section titled “Output”Length of the Box is 10Breadth of the Box is 10Height of the Box is 5Volume of the Box is 500Inheritance
Section titled “Inheritance”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.
Example: A Weighted-Box
Section titled “Example: A Weighted-Box”Let’s create a weighted-box that inherits from box and adds a weight slot and a density method.
main.lisp (Updated with Inheritance)
Section titled “main.lisp (Updated with Inheritance)”;; 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*))Output
Section titled “Output”Length of the Weighted Box is 10Volume of the Weighted Box is 500Weight of the Weighted Box is 2500Density of the Weighted Box is 5.0