🔬 Object-Oriented Programming in Common Lisp

A Comprehensive Guide to CLOS (Common Lisp Object System)

Created by: Andrew J. Pounds, Ph.D.

Welcome to a comprehensive guide on Object-Oriented Programming in Common Lisp using CLOS (the Common Lisp Object System). While Lisp is often associated with functional programming, CLOS provides one of the most powerful and flexible object systems ever designed. CLOS was standardized as part of ANSI Common Lisp in 1994 and influenced the design of object systems in many other languages.

This document explores how Common Lisp approaches OOP concepts and provides practical examples using SBCL (Steel Bank Common Lisp). By understanding CLOS, you'll be able to write modular, maintainable, and highly flexible code that takes advantage of features like multiple inheritance, generic functions, and method combinations.

📚 Learning Objectives

After completing this exercise, you will be able to:

❓ What is Object-Oriented Programming?

Object-Oriented Programming is a paradigm that organizes code around "objects" that contain both data (variables) and functions (methods). Think of an object like a blueprint: a Drink class defines what properties all drinks have and what operations can be performed on them.

The core principles of OOP are:

CLOS supports all of these principles, but approaches them differently than languages like Java or C++. The key differences are:

📊 CLOS vs. Other OOP Languages

Here's how CLOS compares to other popular object-oriented languages:

Feature Java C++ Common Lisp (CLOS)
Class/Type Definition class ClassName class ClassName (defclass class-name ...)
Instance Creation new ClassName() new ClassName() (make-instance 'class-name)
Method Definition Inside class body Inside class body (defmethod name ...) (outside class)
Method Dispatch Single dispatch (on this) Single dispatch (on this) Multiple dispatch (on any/all arguments)
Inheritance Single (+ interfaces) Multiple Multiple
Access Modifiers public, private, protected public, private, protected By convention and accessor functions
Memory Management Automatic (GC) Manual or smart pointers Automatic (GC)
This/Self Reference this this Explicit parameter in method
📝 Note: The most significant difference is that in CLOS, methods are not defined inside classes. Instead, you define generic functions and then provide methods that specialize on particular classes. This separation allows for powerful features like multiple dispatch and method combinations.

✏️ Example 1: Basic Class Definition

Let's start with a simple class definition. In CLOS, we use defclass to define a class and its slots (instance variables):

; drink-basic.lisp
; Basic class definition in CLOS

;; ============================================================
;; CLASS DEFINITION
;; ============================================================

;; DEFCLASS defines a new class
;; Syntax: (defclass class-name (superclasses) (slot-definitions) options)

(defclass drink ()
  ;; The empty () means no superclasses (other than the default T)
  
  ;; Slot definitions - these are like instance variables
  ((name
    ;; :initarg - keyword used when creating instances
    :initarg :name
    ;; :initform - default value if not specified
    :initform "Unknown"
    ;; :accessor - creates both getter and setter methods
    :accessor drink-name)
   
   (cost
    :initarg :cost
    :initform 1.75
    :accessor drink-cost)
   
   ;; A constant-like slot (we won't provide a setter)
   (maximum
    :initform 10
    ;; :reader - creates only a getter (no setter)
    :reader drink-maximum)))

;; ============================================================
;; MAIN PROGRAM
;; ============================================================

(defun main ()
  ;; Create an instance using MAKE-INSTANCE
  ;; :name and :cost are the :initarg keywords we defined
  (let ((my-drink (make-instance 'drink 
                                  :name "Coke" 
                                  :cost 1.75)))
    
    ;; Access slots using the accessor functions
    (format t "Drink name: ~a~%" (drink-name my-drink))
    (format t "Drink cost: $~,2f~%" (drink-cost my-drink))
    (format t "Maximum capacity: ~a~%" (drink-maximum my-drink))
    
    ;; Modify a slot using the accessor (setf form)
    (setf (drink-cost my-drink) 2.00)
    (format t "New cost: $~,2f~%" (drink-cost my-drink))))

(main)

Run with:

sbcl --script drink-basic.lisp

Output:

Drink name: Coke
Drink cost: $1.75
Maximum capacity: 10
New cost: $2.00
💡 Key Observations:

📋 Slot Options Reference

Here are the most commonly used slot options in CLOS:

Option Description Example
:initarg Keyword used when creating instances :initarg :name
:initform Default value expression :initform 0
:accessor Creates reader and writer functions :accessor drink-name
:reader Creates only a reader function :reader drink-maximum
:writer Creates only a writer function :writer set-drink-name
:documentation Documentation string for the slot :documentation "The drink's name"
:type Expected type (for documentation/optimization) :type string

✏️ Example 2: Generic Functions and Methods

In CLOS, methods are defined separately from classes using defgeneric and defmethod. This is the key difference from Java/C++ style OOP:

; drink-methods.lisp
; Demonstrating generic functions and methods in CLOS

;; ============================================================
;; CLASS DEFINITION
;; ============================================================

(defclass drink ()
  ((name
    :initarg :name
    :initform "Unknown"
    :accessor drink-name)
   (cost
    :initarg :cost
    :initform 1.75
    :accessor drink-cost)
   (available
    :initform 0
    :accessor drink-available)
   (consumed
    :initform 0
    :accessor drink-consumed)
   (money-made
    :initform 0.0
    :accessor drink-money-made)
   (maximum
    :initform 10
    :reader drink-maximum)))

;; ============================================================
;; GENERIC FUNCTION DECLARATIONS (optional but recommended)
;; ============================================================

;; DEFGENERIC declares a generic function
;; This is optional - DEFMETHOD will create it automatically
;; But it's good practice for documentation

(defgeneric vend (drink)
  (:documentation "Dispense a drink if available."))

(defgeneric refill (drink)
  (:documentation "Restock the drink to maximum capacity."))

(defgeneric profit (drink)
  (:documentation "Return the total money made from this drink."))

;; ============================================================
;; METHOD DEFINITIONS
;; ============================================================

;; DEFMETHOD defines a method that specializes on a class
;; The (drink drink) means: parameter 'drink' must be of class 'drink'

(defmethod vend ((d drink))
  "Dispense one drink if available, otherwise show sold out."
  ;; Check if drinks are available
  (if (> (drink-available d) 0)
      ;; If available, dispense one
      (progn
        ;; Decrease available count
        (setf (drink-available d) (1- (drink-available d)))
        ;; Increase consumed count
        (setf (drink-consumed d) (1+ (drink-consumed d)))
        ;; Add to money made
        (setf (drink-money-made d) 
              (+ (drink-money-made d) (drink-cost d)))
        ;; Print purchase message
        (format t "~a Purchased.~%" (drink-name d)))
      ;; If not available, show sold out
      (progn
        (format t "~%")
        (format t "  \\\\\\\\\\\\~%")
        (format t "  \\ SOLD OUT \\~%")
        (format t "  \\\\\\\\\\\\~%")
        (format t "~%"))))

(defmethod refill ((d drink))
  "Restock this drink to maximum capacity."
  (setf (drink-available d) (drink-maximum d)))

(defmethod profit ((d drink))
  "Return the total money made, rounded to cents."
  ;; Round to 2 decimal places
  (/ (round (* (drink-money-made d) 100)) 100.0))

(defmethod drinks-sold ((d drink))
  "Return the number of drinks sold."
  (drink-consumed d))

(defmethod restock-amount ((d drink))
  "Return the number of drinks needed to reach full capacity."
  (- (drink-maximum d) (drink-available d)))

;; ============================================================
;; MAIN PROGRAM
;; ============================================================

(defun main ()
  ;; Create a drink instance
  (let ((coke (make-instance 'drink :name "Coke" :cost 1.75)))
    
    ;; Refill the machine
    (refill coke)
    (format t "After refill, ~a available: ~a~%" 
            (drink-name coke) 
            (drink-available coke))
    
    ;; Vend some drinks
    (format t "~%Vending drinks:~%")
    (vend coke)
    (vend coke)
    (vend coke)
    
    ;; Show results
    (format t "~%Results:~%")
    (format t "Drinks sold: ~a~%" (drinks-sold coke))
    (format t "Profit: $~,2f~%" (profit coke))
    (format t "Need to restock: ~a~%" (restock-amount coke))))

(main)

Output:

After refill, Coke available: 10

Vending drinks:
Coke Purchased.
Coke Purchased.
Coke Purchased.

Results:
Drinks sold: 3
Profit: $5.25
Need to restock: 3
🔑 Key Point: Method Specialization In the method definition (defmethod vend ((d drink)) ...), the (d drink) part means: This method will only be called when the argument is of class drink (or a subclass).

✏️ Example 3: Custom Constructor Functions

While make-instance works fine, it's common to create convenience constructor functions that provide a cleaner interface:

; drink-constructor.lisp
; Demonstrating custom constructor functions in CLOS

(defclass drink ()
  ((name
    :initarg :name
    :initform "Unknown"
    :accessor drink-name)
   (cost
    :initarg :cost
    :initform 1.75
    :accessor drink-cost)
   (available
    :initform 0
    :accessor drink-available)
   (consumed
    :initform 0
    :accessor drink-consumed)
   (money-made
    :initform 0.0
    :accessor drink-money-made)
   (maximum
    :initform 10
    :reader drink-maximum)))

;; ============================================================
;; CUSTOM CONSTRUCTOR FUNCTIONS
;; ============================================================

;; Constructor with just name (uses default cost of $1.75)
(defun make-drink (name)
  "Create a new drink with the given name and default cost."
  (make-instance 'drink :name name))

;; Constructor with name and custom cost
(defun make-drink-with-cost (name cost)
  "Create a new drink with the given name and cost."
  (make-instance 'drink :name name :cost cost))

;; ============================================================
;; METHOD DEFINITIONS
;; ============================================================

(defmethod vend ((d drink))
  "Dispense one drink if available."
  (if (> (drink-available d) 0)
      (progn
        (setf (drink-available d) (1- (drink-available d)))
        (setf (drink-consumed d) (1+ (drink-consumed d)))
        (setf (drink-money-made d) 
              (+ (drink-money-made d) (drink-cost d)))
        (format t "~a Purchased.~%" (drink-name d)))
      (format t "~a: SOLD OUT!~%" (drink-name d))))

(defmethod refill ((d drink))
  "Restock to maximum capacity."
  (setf (drink-available d) (drink-maximum d)))

;; ============================================================
;; MAIN PROGRAM
;; ============================================================

(defun main ()
  ;; Create drinks using our custom constructors
  (let ((coke (make-drink "Coke"))                    ; Uses default $1.75
        (fancy-water (make-drink-with-cost "Fiji Water" 4.50)))  ; Custom price
    
    ;; Show what we created
    (format t "Created drinks:~%")
    (format t "  ~a at $~,2f~%" (drink-name coke) (drink-cost coke))
    (format t "  ~a at $~,2f~%" (drink-name fancy-water) (drink-cost fancy-water))
    
    ;; Refill both
    (refill coke)
    (refill fancy-water)
    
    ;; Vend some
    (format t "~%Vending:~%")
    (vend coke)
    (vend fancy-water)
    (vend fancy-water)))

(main)

Output:

Created drinks:
  Coke at $1.75
  Fiji Water at $4.50

Vending:
Coke Purchased.
Fiji Water Purchased.
Fiji Water Purchased.
💡 Tip: Custom constructor functions like make-drink are a common pattern in Lisp. They provide a cleaner API than make-instance with keyword arguments and can include validation or setup logic.

🥤 The Complete Vending Machine Example

Now let's look at a complete, working example that puts everything together. This program implements a vending machine that tracks drink sales and profits.

; vending-machine.lisp
; Complete Vending Machine Example using CLOS
;
; This program demonstrates:
; - Class definition with defclass
; - Slot definitions with various options
; - Generic functions and methods
; - Custom constructor functions
; - Object interaction
;
; Author: Student Name
; Course: Lisp Programming

;; ============================================================
;; CLASS DEFINITION
;; ============================================================

(defclass drink ()
  ;; The empty () means we have no superclasses
  ;; (other than the implicit T which all classes inherit from)
  
  ((name
    ;; :initarg :name means we can write (make-instance 'drink :name "Coke")
    :initarg :name
    ;; :initform provides a default if :name isn't specified
    :initform "Unknown"
    ;; :accessor creates both a reader (drink-name obj) 
    ;; and a writer (setf (drink-name obj) value)
    :accessor drink-name
    ;; :documentation describes this slot
    :documentation "The name of the drink")
   
   (cost
    :initarg :cost
    :initform 1.75
    :accessor drink-cost
    :documentation "The price of the drink in dollars")
   
   (available
    ;; No :initarg - this is set internally, not at creation time
    :initform 0
    :accessor drink-available
    :documentation "Number of drinks currently in stock")
   
   (consumed
    :initform 0
    :accessor drink-consumed
    :documentation "Total number of drinks sold")
   
   (money-made
    :initform 0.0
    :accessor drink-money-made
    :documentation "Total revenue from this drink")
   
   (maximum
    :initform 10
    ;; :reader creates only a reader function (no setter)
    ;; This makes maximum effectively read-only after creation
    :reader drink-maximum
    :documentation "Maximum capacity for this drink")))

;; ============================================================
;; GENERIC FUNCTION DECLARATIONS
;; ============================================================

;; Declaring generic functions is optional but good practice
;; It documents the interface and allows adding :documentation

(defgeneric vend (drink)
  (:documentation "Dispense a drink if available."))

(defgeneric refill (drink)
  (:documentation "Restock the drink to maximum capacity."))

(defgeneric profit (drink)
  (:documentation "Return the total money made from this drink."))

(defgeneric drinks-sold (drink)
  (:documentation "Return the number of drinks sold."))

(defgeneric restock-amount (drink)
  (:documentation "Return the number needed to reach full capacity."))

;; ============================================================
;; CUSTOM CONSTRUCTOR FUNCTIONS
;; ============================================================

(defun make-drink (name)
  "Create a new drink with the given name and default cost ($1.75).
   
   Arguments:
     name - A string naming the drink
   
   Returns:
     A new drink instance"
  (make-instance 'drink :name name))

(defun make-drink-with-cost (name cost)
  "Create a new drink with the given name and custom cost.
   
   Arguments:
     name - A string naming the drink
     cost - The price in dollars (a number)
   
   Returns:
     A new drink instance"
  (make-instance 'drink :name name :cost cost))

;; ============================================================
;; METHOD DEFINITIONS
;; ============================================================

(defmethod vend ((d drink))
  "Dispense one drink if available.
   
   If drinks are available:
   - Decreases available count by 1
   - Increases consumed count by 1
   - Adds the drink cost to money-made
   - Prints a purchase confirmation
   
   If no drinks available:
   - Prints a SOLD OUT message"
  
  ;; Check if any drinks are available
  (if (> (drink-available d) 0)
      
      ;; Drinks ARE available - dispense one
      (progn
        ;; Decrease available by 1
        ;; (1- x) is shorthand for (- x 1)
        (setf (drink-available d) (1- (drink-available d)))
        
        ;; Increase consumed by 1
        ;; (1+ x) is shorthand for (+ x 1)
        (setf (drink-consumed d) (1+ (drink-consumed d)))
        
        ;; Add cost to money-made
        (setf (drink-money-made d) 
              (+ (drink-money-made d) (drink-cost d)))
        
        ;; Print purchase message
        (format t "~a Purchased.~%" (drink-name d)))
      
      ;; Drinks NOT available - show sold out
      (progn
        (format t "~%")
        (format t "   \\\\\\\\\\\\\\\\\\\\~%")
        (format t "   \\ SOLD OUT \\~%")
        (format t "   \\\\\\\\\\\\\\\\\\\\~%")
        (format t "~%"))))

(defmethod refill ((d drink))
  "Restock this drink to maximum capacity.
   Sets available to the maximum value."
  (setf (drink-available d) (drink-maximum d)))

(defmethod profit ((d drink))
  "Return the total money made, rounded to cents.
   
   We round to avoid floating-point display issues."
  ;; Multiply by 100, round, divide by 100 to get 2 decimal places
  (/ (round (* (drink-money-made d) 100)) 100.0))

(defmethod drinks-sold ((d drink))
  "Return the total number of drinks sold."
  (drink-consumed d))

(defmethod restock-amount ((d drink))
  "Return the number of drinks needed to reach full capacity."
  (- (drink-maximum d) (drink-available d)))

;; ============================================================
;; MAIN PROGRAM
;; ============================================================

(defun main ()
  "Main entry point for the vending machine simulation."
  
  ;; Create drink objects using our constructor functions
  ;; LET* allows each binding to see the previous ones
  (let* ((drink1 (make-drink "Coke"))
         (drink2 (make-drink "Sprite"))
         (drink3 (make-drink "Doctor Pepper"))
         ;; drink4 uses the custom cost constructor
         (drink4 (make-drink-with-cost "V8 Splash" 4.25)))
    
    ;; Refill all drinks to maximum capacity
    (refill drink1)
    (refill drink2)
    (refill drink3)
    (refill drink4)
    
    ;; Simulate purchases
    
    ;; Purchase 8 Cokes
    (vend drink1)
    (vend drink1)
    (vend drink1)
    (vend drink1)
    (vend drink1)
    (vend drink1)
    (vend drink1)
    (vend drink1)
    
    ;; Purchase 6 Sprites
    (vend drink2)
    (vend drink2)
    (vend drink2)
    (vend drink2)
    (vend drink2)
    (vend drink2)
    
    ;; Purchase 2 Doctor Peppers
    (vend drink3)
    (vend drink3)
    
    ;; Try to purchase 11 V8 Splash (only 10 available)
    (vend drink4)
    (vend drink4)
    (vend drink4)
    (vend drink4)
    (vend drink4)
    (vend drink4)
    (vend drink4)
    (vend drink4)
    (vend drink4)
    (vend drink4)
    (vend drink4)  ; This one will fail - SOLD OUT
    
    ;; Calculate and display totals
    (let ((total-sold (+ (drinks-sold drink1)
                         (drinks-sold drink2)
                         (drinks-sold drink3)
                         (drinks-sold drink4)))
          (total-profit (+ (profit drink1)
                           (profit drink2)
                           (profit drink3)
                           (profit drink4))))
      
      ;; Display summary
      (format t "~%")
      (format t "A Total of ~a drinks were sold for a profit of $~,2f~%" 
              total-sold total-profit)
      (format t "~%")
      
      ;; Display restocking information
      (format t "~a ~a needed to restock machine~%" 
              (restock-amount drink1) (drink-name drink1))
      (format t "~a ~a needed to restock machine~%" 
              (restock-amount drink2) (drink-name drink2))
      (format t "~a ~a needed to restock machine~%" 
              (restock-amount drink3) (drink-name drink3))
      (format t "~a ~a needed to restock machine~%" 
              (restock-amount drink4) (drink-name drink4)))))

;; Run the main function
(main)

⚡ Running the Program

Save the code as vending-machine.lisp and run with:

sbcl --script vending-machine.lisp

Sample Output

Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Coke Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Sprite Purchased.
Doctor Pepper Purchased.
Doctor Pepper Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.
V8 Splash Purchased.

   \\\\\\\\\\
   \ SOLD OUT \
   \\\\\\\\\\

A Total of 26 drinks were sold for a profit of $70.50

2 Coke needed to restock machine
4 Sprite needed to restock machine
8 Doctor Pepper needed to restock machine
0 V8 Splash needed to restock machine

📈 Inheritance in CLOS

CLOS supports inheritance, including multiple inheritance. Here's an example of single inheritance:

; inheritance.lisp
; Demonstrating inheritance in CLOS

;; ============================================================
;; BASE CLASS
;; ============================================================

(defclass beverage ()
  ((name
    :initarg :name
    :initform "Unknown"
    :accessor beverage-name)
   (cost
    :initarg :cost
    :initform 1.00
    :accessor beverage-cost)))

;; Generic function for displaying
(defgeneric display-info (beverage))

(defmethod display-info ((b beverage))
  "Display basic beverage information."
  (format t "Beverage: ~a at $~,2f~%" 
          (beverage-name b) 
          (beverage-cost b)))

;; ============================================================
;; DERIVED CLASS
;; ============================================================

;; CaffeinatedDrink inherits from beverage
;; Put the parent class(es) in the second argument list
(defclass caffeinated-drink (beverage)
  ;; Additional slot specific to caffeinated drinks
  ((caffeine-mg
    :initarg :caffeine-mg
    :initform 0
    :accessor drink-caffeine)))

;; Override the display-info method for caffeinated drinks
(defmethod display-info ((d caffeinated-drink))
  "Display caffeinated drink information, including caffeine content."
  ;; First, call the parent method using CALL-NEXT-METHOD
  (call-next-method)
  ;; Then add our extra information
  (format t "  Contains ~a mg of caffeine~%" (drink-caffeine d)))

;; ============================================================
;; MAIN PROGRAM
;; ============================================================

(defun main ()
  ;; Create a basic beverage
  (let ((water (make-instance 'beverage 
                               :name "Spring Water" 
                               :cost 1.50)))
    (format t "=== Basic Beverage ===~%")
    (display-info water))
  
  (format t "~%")
  
  ;; Create a caffeinated drink
  (let ((coffee (make-instance 'caffeinated-drink
                                :name "Espresso"
                                :cost 3.50
                                :caffeine-mg 150)))
    (format t "=== Caffeinated Drink ===~%")
    (display-info coffee))
  
  (format t "~%")
  
  ;; Demonstrate that caffeinated-drink has inherited slots
  (let ((cola (make-instance 'caffeinated-drink
                              :name "Cola"
                              :cost 1.75
                              :caffeine-mg 35)))
    (format t "=== Another Caffeinated Drink ===~%")
    (format t "Name: ~a~%" (beverage-name cola))     ; Inherited accessor
    (format t "Cost: $~,2f~%" (beverage-cost cola))  ; Inherited accessor
    (format t "Caffeine: ~a mg~%" (drink-caffeine cola))))  ; Own accessor

(main)

Output:

=== Basic Beverage ===
Beverage: Spring Water at $1.50

=== Caffeinated Drink ===
Beverage: Espresso at $3.50
  Contains 150 mg of caffeine

=== Another Caffeinated Drink ===
Name: Cola
Cost: $1.75
Caffeine: 35 mg
📝 Note: call-next-method is CLOS's way of calling the parent's version of a method, similar to super.method() in Java. It follows the method resolution order to find the next applicable method.

❌ Common Mistakes and Solutions

❌ Mistake 1: Forgetting the Quote on Class Name

; WRONG - will try to evaluate 'drink' as a variable
(make-instance drink :name "Coke")

; Error: The variable DRINK is unbound.

✅ Correct

; RIGHT - quote the class name
(make-instance 'drink :name "Coke")

❌ Mistake 2: Using Wrong Accessor Syntax

; WRONG - this is Java/C++ syntax
my-drink.name
my-drink->name

; WRONG - wrong order for setf
(drink-name my-drink "New Name")

✅ Correct

; RIGHT - reading
(drink-name my-drink)

; RIGHT - writing (setf form)
(setf (drink-name my-drink) "New Name")

❌ Mistake 3: Missing Specializer in Method Definition

; WRONG - no class specialization
(defmethod vend (d)
  ...)
; This creates a method that accepts ANY object, not just drinks

✅ Correct

; RIGHT - specialize on the drink class
(defmethod vend ((d drink))
  ...)

❌ Mistake 4: Confusing :initarg Name with Slot Name

; Class definition
(defclass drink ()
  ((name :initarg :drink-name ...)))

; WRONG - using slot name instead of initarg
(make-instance 'drink :name "Coke")
; The slot won't be initialized!

✅ Correct

; RIGHT - use the :initarg keyword
(make-instance 'drink :drink-name "Coke")

; OR better - make initarg match slot name
(defclass drink ()
  ((name :initarg :name ...)))
(make-instance 'drink :name "Coke")

❌ Mistake 5: Trying to Modify a Read-Only Slot

; Slot defined with :reader only
(defclass drink ()
  ((maximum :initform 10 :reader drink-maximum)))

; WRONG - can't setf a reader
(setf (drink-maximum my-drink) 20)
; Error: (SETF DRINK-MAXIMUM) is undefined

✅ Correct

; If you need to modify it, use :accessor instead of :reader
(defclass drink ()
  ((maximum :initform 10 :accessor drink-maximum)))

; Now this works
(setf (drink-maximum my-drink) 20)

📚 Quick Reference

Task Common Lisp (CLOS) Syntax
Define a class (defclass name (supers) (slots))
Create an instance (make-instance 'class :slot value)
Define slot with accessor (slot-name :accessor accessor-name)
Define slot with reader only (slot-name :reader reader-name)
Set default value :initform value
Set initialization keyword :initarg :keyword-name
Read a slot (accessor-name object)
Write a slot (setf (accessor-name object) value)
Declare generic function (defgeneric name (params))
Define a method (defmethod name ((p class)) body)
Call parent method (call-next-method)
Check object's class (typep object 'class-name)
Get object's class (class-of object)

⭐ Key Takeaways About OOP in Common Lisp

💻 Programming Exercise

Objective: Modify the vending machine program to replace the V8 Splash drink with a new beverage of your choice.

Assignment:

The current program includes V8 Splash at $4.25 as the fourth drink. Your task is to:

  1. Replace V8 Splash: Change the drink name to something different (e.g., "Orange Juice", "Iced Tea", "Lemonade", etc.) and choose a new price (different from $4.25)
  2. Modify the vending operations: Change how many times each drink is purchased by modifying the number of (vend ...) calls for each drink
  3. Test your program: Run it with SBCL to ensure it works properly

Specific Changes to Make:

In the main function, change the line:

(drink4 (make-drink-with-cost "V8 Splash" 4.25))

to your new drink with a new price, for example:

(drink4 (make-drink-with-cost "Orange Juice" 3.50))

Requirements:

Testing Your Solution:

  1. Make your changes to the program
  2. Save the file
  3. Run with: sbcl --script vending-machine.lisp
  4. Verify the output shows your new drink name and correct profit calculations

🎯 Challenge:

Before running your program, calculate what the total profit should be. Does your output match your prediction?

Hint: Total profit = (# of Cokes × $1.75) + (# of Sprites × $1.75) + (# of Dr Peppers × $1.75) + (# of your drink × your price)