7

BEYOND LISTS

Lists are the most important data structure in Lisp, but Common Lisp includes the very best object system for doing object-oriented programming, hash-tables, arrays, and structures. Lists can do everything they do, but for speed and efficiency purposes, you'll want to use these at some point.

OOP

OOP is done through the CLOS–the Common Lisp Object System.

In CLOS, classes are user-defined data types that group related data. Unlike the OOP systems found elsewhere, Common Lisp classes don't package data and an API for acting on that data together in the same module. Instead, classes define types of data that can be targeted or specialized for by typecase forms and generic functions/methods. The "verbs" for an object live separately.

Class Basics

(defclass creature ()
  ((name :initarg :name
         :initform nil
         :accessor creature-name)
   (territory :initarg :territory
              :initform "Earth"
              :accessor creature-territory)))

This is the basic structure of a class definition. defclass takes at least three arguments: a name, a list of classes to inherit from, and slot definitions.

Use make-instance to make an instance of the class.

(make-instance 'creature :name "Cat")

Slot definitions can take a number of options.

:initargs defines the keyword argument used for setting the slot's value when creating an instance of the class. In the example below, :name is the :initarg.

(defparameter *cat* (make-instance 'creature :name "Cat"))

:initform defines the default value of the slot if none is provided at the time of instantiation.

(creature-territory *cat*)
Result"Earth"

When a class inherits from another class or classes, its definition includes everything in the superclass(es) plus the additional slots it's own definition includes. You can also overwrite the definition of a superclass's slot.

(defclass person (creature)
  ((name :initarg :creature-name
         :initform "John Doe"
         :accessor person-name)
   (marrital-status :initarg :marrital-status
                    :initform :single
                    :accessor marrital-status)))

(defparameter *john-doe* (make-instance 'person :marrital-status :married))

The name slot falls back to its :initform:

(person-name *john-doe*)
Result"John Doe"

:accessor defines the generic method that can be used to both access and modify the slot's value.

(creature-name *cat*)
Result"Cat"
(setf (creature-name *cat*) "Tiger")

(creature-name *cat*)
Result"Tiger"

Because accessors are generic methods specialized on instances of the class that defines them, two classes can define accessors with the same name without any conflicts.

In practice, you'll often see accessors that follow a <class-name>-<slot-name> convention for clarity.

Usually you want an accessor–a method that allows you to both read and write to a slot. In some cases, as in condition definitions, you may want to only allow the program to read from a slot. In that case, you can replace :accessor with :reader. Of course, you can define a writer with :writer.

Custom Constructors

While you can manually called make-instance all the time, it's common to define a custom constructor for objects.

(defun make-creature (name territory)
  (make-instance 'creature :name name :territory territory))

In addition to reducing typing in the simple case, custom constructors are also where you can filter input, add type-checking with etypecase, modify data before making an instance, etc.

Generic Functions & Methods

Classes describe what an object has, but they don't say anything about what an object does. That's because in Common Lisp, classes don't have methods; generic functions have methods.

(defclass person ()
  ((name :initarg :name :initform nil :accessor person-name)))
(defgeneric greet (instance))
(defmethod greet ((this person))
  (format t "~&Hello, ~a.~%" (person-name this)))
(defparameter *john-doe* (make-instance 'person :name "John Doe"))

Call it:

(greet *john-doe*)
ResultHello, John Doe
NIL

defgeneric defines a generic function. Generic functions define an interface for, and then dispatch to, generic methods. greet defines an interface: it only requires an instance of a class. When a call to greet is made, it looks at the object instance passed and dispatches to the method specialized on that generic function.

Multiple Inheritance & Multiple Dispatch

Common Lisp's object system supports both multiple inheritance and multiple dispatch

(defclass flying ()
    ((max-altitude :initarg :max-altitude :initform 1000 :accessor max-altitude)))

(defclass fire-breathing ()
    ((flame-range :initarg :flame-range :initform 30 :accessor flame-range)))

(defclass armored ()
    ((armor-rating :initarg :armor-rating :initform 50 :accessor armor-rating)))

(defclass dragon (flying fire-breathing) ())
(defclass warrior (armored) ())
(defclass mage (flying) ())

Now multiple dispatch interacts with the inheritance:

(defgeneric attack (attacker target))

(defmethod attack ((attacker fire-breathing) (target armored))
  (format t "Fire hits armor! ~a% deflected.~%" (armor-rating target)))

(defmethod attack ((attacker flying) (target flying))
  (format t "Aerial battle at ~am altitude!~%" (min (max-altitude attacker) (max-altitude target))))

(defmethod attack ((attacker dragon) (target warrior))
  (format t "The dragon dives and breathes fire on the warrior!~%")
  (call-next-method))  ;; also runs fire-breathing vs armored

(defparameter *smaug* (make-instance 'dragon :max-altitude 5000 :flame-range 100))
(defparameter *aragorn* (make-instance 'warrior :armor-rating 80))
(defparameter *gandalf* (make-instance 'mage :max-altitude 200))
(attack *smaug* *aragorn*)
ResultThe dragon dives and breathes fire on the warrior!
Fire hits armor! 80% deflected.
=> NIL
(attack *smaug* *gandalf*)
ResultAerial battle at 200m altitude!
=> NIL
(attack *gandalf* *smaug*)
ResultAerial battle at 200m altitude!
=> NIL

Before, After, & Around Methods

Every generic method can be wrapped in code that will modify its behavior before it's called, after it's called, or both ("around"). Methods can be specialized on more than one type. A method call is sent to the method that is most "specific"–the method that specializes on types that are closest to the ones passed to the method as argument. A more specific method can call the next less specific method in a chain of methods.

Method combination is a technique that involves chaining together some mixture of the above techniques. Extending an OOP system in Common Lisp will involve some degree of method combination.

To modify the behavior of a method before it's called, you can make a :before method:

(defmethod greet :before ((this person))
  (format t "~&Oh, what a nice looking young man. Let me go talk to him...~%"))
(greet *john-doe*)
ResultOh, what a nice looking young man. Let me go talk to him...
Hello, John Doe.
NIL

To modify its behavior after it's called, make an :after method:

(defmethod greet :after ((this person))
  (format t "~&(Strong handshake, good man.)~%"))
(greet *john-doe*)
ResultOh, what a nice looking young man. Let me go talk to him...
Hello, John Doe.
(Strong handshake, good man.)
NIL

Before and after methods are fairly self explanatory. Primary methods are the core of the onion; before and after methods are two layers around the core.

Around methods are a layer outside of before and after methods. They can do something before both entry from :before methods and after exit from :after methods.

(defmethod greet :around ((this person))
  (format t "I should get off the couch and go talk to people."))
(greet *john-doe*)
ResultI should get off the couch and go talk to people.
NIL

However, they require extra work to use.

(defmethod greet :around ((this person))
  (format t "I should get off the couch and go talk to people.")
  (call-next-method)
  (format t "Here's my card, ~a. Let me know if you need help learning Lisp." (person-name this)))
(greet *john-doe*)
ResultI should get off the couch and go talk to people. Oh, what a nice looking
young man. Let me go talk to him... Hello, John Doe. (Strong handshake, good
man.) Here's my card, John Doe. Let me know if you need help learning Lisp.
NIL

call-next-method calls the next most specific version of greet for the *john-doe* object, which is the before method. The before method automatically calls the next method–the primary method–so we don't have to call call-next-method in it. Same goes for the around method.

Since call-next-method works by calling the next most specific method, it can be used to chain together methods in other ways.

(defclass smart-person (person) ())
(defclass rich-person (person) ())
(defclass dumb-person (person) ())
(defclass poor-person (person) ())

(defparameter *musk* (make-instance 'smart-person :name "Mr. Musk"))

Without call-next-method, only the most specific primary method runs:

(defmethod greet ((this smart-person))
  (format t "~&(Wow, it's ~a! I can't believe he's here!)~%" (person-name this)))
(greet *musk*)
ResultI should get off the couch and go talk to people. Oh, what a nice looking
young man. Let me go talk to him... (Wow, it's Mr. Musk! I can't believe
he's here!) (Strong handshake, good man.) Here's my card, Mr. Musk. Let me
know if you need help learning Lisp.
NIL

With call-next-method, the next most specific method also runs:

(defmethod greet ((this smart-person))
  (format t "~&(Wow, it's ~a! I can't believe he's here!)~%" (person-name this))
  (call-next-method))
(greet *musk*)
ResultI should get off the couch and go talk to people. Oh, what a nice looking
young man. Let me go talk to him... (Wow, it's Mr. Musk! I can't believe
he's here!) Hello, Mr. Musk (Strong handshake, good man.) Here's my card,
Mr. Musk. Let me know if you need help learning Lisp.
NIL

All this indirection can be confusing. If you are extending some OOP system and your method isn't being called for some reason, you can use compute-applicable-methods to find out which methods a particular combination of arguments to a certain function name apply to those arguments.

(compute-applicable-methods #'greet (list (make-instance 'person :name "Micah")))
Result(#<STANDARD-METHOD COMMON-LISP-USER::GREET :AROUND (PERSON) {7009D091C3}>
 #<STANDARD-METHOD COMMON-LISP-USER::GREET :AFTER (PERSON) {7009D091E3}>
 #<STANDARD-METHOD COMMON-LISP-USER::GREET :BEFORE (PERSON) {7009D09203}>
 #<STANDARD-METHOD COMMON-LISP-USER::GREET (PERSON) {7009D09223}>)
(compute-applicable-methods #'greet (list (make-instance 'smart-person :name "Mr. Musk")))
Result(#<STANDARD-METHOD COMMON-LISP-USER::GREET (SMART-PERSON) {700792E213}>
 #<STANDARD-METHOD COMMON-LISP-USER::GREET :AROUND (PERSON) {7009D091C3}>
 #<STANDARD-METHOD COMMON-LISP-USER::GREET :AFTER (PERSON) {7009D091E3}>
 #<STANDARD-METHOD COMMON-LISP-USER::GREET :BEFORE (PERSON) {7009D09203}>
 #<STANDARD-METHOD COMMON-LISP-USER::GREET (PERSON) {7009D09223}>)

Pretty Printing Objects

There are several generic functions worth knowing about when you first start using Lisp. One of them is print-object–used for pretty printing objects.

(defmethod print-object ((this creature) stream)
  (with-slots (name territory) this
    (format stream "~a ~a" name territory)))

Now instances print nicely:

(make-instance 'creature :name "Cat" :territory "House")
ResultCat House

with-slots is a useful macro for dealing with object data. You could use let instead…

(let ((name (slot-value this 'name))
      (territory (slot-value this 'territory)))
  ...)

But with-slots is nicer for this purpose. There is also with-accessors, which is equivalent to using creature-name and creature-territory instead of slot-value.

In the case of print-object, it's safest to stick with with-slots or slot-value. If you change the accessor, you might end up with difficult to debug errors when printing objects.

Another common error would be to try to print slot values that aren't bound. That's why you should probably have :initform set to nil if you have no other desired default value; you won't have trouble with UNBOUND SLOT errors.

(defclass beverage ()
  ((name :initarg :name :accessor name)
   (amount :initarg :amount :accessor amount)))

(make-instance 'beverage)
Result#<BEVERAGE {700B0D0343}>
(defmethod print-object ((this beverage) stream)
  (with-slots (name amount) this
    (format stream "~a ~a" name amount)))

(make-instance 'beverage)
Result#<BEVERAGE {7008470343}>

Since we didn't bind any of the slots–neither in the call to make-instance nor in the class definition with :initform–we get the <<error printing object>> message. We have two choices to fix the problem: check for unbound slots in the print-object method using slot-boundp, or prevent slots from being unbound when the object is initialized.

;; Check for unbound slots.
(defmethod print-object ((this beverage) stream)
  (let ((name (when (slot-boundp this 'name) (slot-value this 'name)))
        (amount (when (slot-boundp this 'amount) (slot-value this 'amount))))
    (format stream "~a ~a" name amount)))

(make-instance 'beverage)
ResultNIL NIL
;; Prevent slots from being unbound by specifying a default value of nil.
(defclass beverage ()
  ((name :initarg :name :initform nil :accessor name)
   (amount :initarg :amount :initform nil :accessor amount)))

(defmethod print-object ((this beverage) stream)
  (with-slots (name amount) this
    (format stream "~a ~a" name amount)))

(make-instance 'beverage)
ResultNIL NIL

Even Better Printing

By default, Lisp will provide a printed representation of an object that looks like this:

#<BEVERAGE {700B0D0343}>

This representation contains two important piece of information:

  1. #<BEVERAGE ...>: This is a BEVERAGE object.
  2. {700B0D0343}: This is where the object is in memory.

Consider this:

(defclass warm-beverage (beverage) ())
(defclass cold-beverage (beverage) ())

They print identically:

(values (make-instance 'warm-beverage :name "Coffee" :amount 1000)
        (make-instance 'cold-beverage :name "Coffee" :amount 1000))
ResultCoffee 1000, Coffee 1000

There's no indication that there is a difference between these two objects. If they weren't shown side-by-side, we wouldn't even know they were two different objects.

To add this information back into your custom pretty printers, you can use the print-unreadable-object macro.

(defmethod print-object ((this beverage) stream)
  (print-unreadable-object (this stream :type t :identity t)
    (with-slots (name amount) this
      (format stream "~a ~a" name amount))))

Now instances print with type and identity info:

(make-instance 'cold-beverage :name "Coffee" :amount 1000)
Result#<COLD-BEVERAGE Coffee 1000 {7008A403A3}>

Modifying Object Initialization

Let's say we have the following defclass:

(defclass user ()
  ((name :initarg :name
         :initform nil
         :accessor user-name)
   (email :initarg :email
          :initform nil
          :accessor user-email)))

We've specified the type of data the slots will hold: strings.

Let's run make-instance:

(make-instance 'user)
Result#<USER {7007C40343}>

It works, but what we really want is:

  1. Both slots to be required.
  2. To validate that the value assigned in email is actually an email.

We can do that using two important features of Common Lisp's object system: custom initializer methods and method modifiers.

To validate that the email is a valid email, we would like to be able to use regular expressions. Lisp doesn't have regular expression support built in, so we'll use the ppcre package to give us regular expressions:

(ql:quickload "cl-ppcre")

Then we'll make a function for validating that the string passed to make-instance is a valid email.

(defun valid-email-address-p (string)
  (not (null
        (ppcre:scan "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" string))))

We can test it:

(valid-email-address-p "[email protected]")
ResultT
(valid-email-address-p "dude*@*man.com")
ResultNIL

Now we just need some way to run this function when we run make-instance. To do that, we can use custom initialization with the initialize-instance method.

(defmethod initialize-instance :before ((this user) &key name email)
  (unless name
    (error "Name is required."))
  (unless email
    (error "Email is required."))
  (unless (valid-email-address-p email)
    (error "~a is not a valid email." email)))

initialize-instance is a generic method run whenever an object is created. Using the :before method modifier, we can run some code before the main initialize-instance method. Here, we ensure that the name and email slots are bound and that the email is valid. Importantly, if the data doesn't pass the checks, the developer is thrown into the debugger, and the object is never created.

Specializing Methods On Other Values

It's possible to specialize methods on other values besides just classes/types.

(defmethod greet ((this (eql :robot)))
  (format t "Woah, this is a realistic robot."))
(greet :robot)
ResultWoah, this is a realistic robot.
NIL
(defmethod greet ((this (eql (+ 2 2))))
  (format t "Hey, Micah."))
(greet 4)
ResultHey, Micah.
NIL
(defmethod greet ((this (eql #'+)))
  (funcall this 1 2 3 4 5))
(greet #'+)
Result15 (4 bits, #xF, #o17, #b1111)

In the section later about structures, I'll show you that methods can even be specialized on structures and don't require you to use classes/types for your data structure.

Unfortunately, you can't specialize a method on things like the type string or the shape of some data structure. For that, there's Coalton.

(defmethod greet ((this (eql 'string)))
  (format t "What's up, ~a?~%" this))

(greet "Dude")
ResultError: There is no applicable method for the generic function
#<STANDARD-GENERIC-FUNCTION COMMON-LISP-USER::GREET (12)> when called with
arguments ("Dude").
   [Condition of type SB-PCL::NO-APPLICABLE-METHOD-ERROR]

Introspection

You can get information about a class or object by various means.

class-of takes an object instance and returns a class instance.

(class-of (make-instance 'smart-person :name "Mr. Musk"))
Result#<STANDARD-CLASS CL-USER::SMART-PERSON>

find-class takes the name of a class and returns a class instance.

(find-class 'smart-person)
Result#<STANDARD-CLASS CL-USER::SMART-PERSON>

class-name returns the name of a class instance.

(class-name (class-of (make-instance 'smart-person :name "Mr. Musk")))
ResultSMART-PERSON

slot-boundp takes an object instance a slot name and returns t if that slot is bound.

(slot-boundp (make-instance 'smart-person :name "Mr. Musk") 'name)
ResultT

slot-exists-p take an object instance and a slot name and returns t if that slot exists.

(slot-exists-p (make-instance 'smart-person :name "Mr. Musk") 'age)
ResultNIL

HASH-TABLES

A hash-table is a group of key/value pairs. associated-lists are the list-based equivalent.

To make a hash-table, use make-hash-table.

(make-hash-table)
Result#<HASH-TABLE :TEST EQL :COUNT 0 {7008090473}>

Common Lisp doesn't have any built-in syntax for creating hash-tables like using {} or anything like that. In fact, you can't even define the initial contents of the hash-table when you make it–you need separate functions for that.

To add an item to a hash-table, use setf + gethash:

(defparameter *h* (make-hash-table))
(setf (gethash :name *h*) "Micah")
ResultMicah

To access values, pass the key and hash-table to gethash.

(gethash :name *h*)
ResultMicah
(gethash :age *h*)
ResultNIL

You can modify the element using the same setf + gethash pattern.

(setf (gethash :name *h*) "Bob")
ResultBob

Compare this to alists:

(defparameter *a* '())
(setf *a* (append *a* '((:name "Micah"))))
*a*
Result((:NAME Micah))
(cadr (assoc :name *a*))
ResultMicah
(cadr (assoc :age *a*))
ResultNIL
(setf (cadr (assoc :name *a*)) "Bob")
*a*
Result((:NAME Bob))

Printing a hash-table

Hash-tables don't get printed nicely like lists do.

*h*
Result#<HASH-TABLE :TEST EQL :COUNT 1 {700AFCC1E3}>
*a*

((:NAME Bob))

Use maphash to print key/value pairs or do other mapping operations.

(defun print-hash (hash-table)
  (maphash (lambda (key value) (format t "~&~a -> ~a~%" key value)) hash-table))

Now we can use it to inspect hash-tables.

(print-hash *h*)
ResultNAME -> Bob
=> NIL
(setf (gethash :age *h*) 39)
Result39
(print-hash *h*)
ResultNAME -> Bob
AGE -> 39
 => NIL

You can also use the loop macro to iterate through a hash-table, but we'll cover that more later in the chapter on loop.

Using strings as keys

The keys of a hash-table can be keywords, strings, or symbols. But, there's a catch:

(setf (gethash 'favorite-language *h*) "Common Lisp")
(print-hash *h*)
ResultNAME -> Bob
AGE -> 39
FAVORITE-LANGUAGE -> Common Lisp
 => NIL
(gethash 'favorite-language *h*)
ResultCommon Lisp
(setf (gethash "location" *h*) "Japan")
(print-hash *h*)
ResultNAME -> Bob
AGE -> 39
FAVORITE-LANGUAGE -> Common Lisp
location -> Japan
 => NIL
(gethash "location" *h*)
ResultNIL

make-hash-table, when searching for "location" or any other key, defaults to testing keys using eql. As I wrote previously, eql tests if objects are stored in the same place in memory unless their characters or numbers of the same type.

(eql 'favorite-language 'favorite-language)
ResultT
(eql "location" "location")
ResultNIL

You can change the test to equal, equalp, or eq.

With equal, you can compare the "structure" of two objects:

(defparameter *h* (make-hash-table :test #'equal))
(setf (gethash "location" *h*) "Japan")
(gethash "location" *h*)
ResultJapan
(gethash "LOCATION" *h*)
ResultNIL

With equalp, your strings can be different cases:

(setf *h* (make-hash-table :test #'equalp))
(setf (gethash "location" *h*) "Japan")
(gethash "location" *h*)
ResultJapan
(gethash "LOCatiON" *h*)
ResultJapan

ARRAYS

One of the most fun things about Common Lisp is that it is the highest-level language to exist (thanks to macros), but it is also surprisingly low level as well.

In Lisp, a list is a linked-list. Arrays, on the other hand, are surprisingly similar to the arrays in C. You need to manually set their size at creation time. Resizing them involves either assigning them a special property or simply recreating the array with the new size.

Arrays are useful when you have a prototype using lists and need to optimize for speed and memory efficiency.

There are several different kinds of arrays. They include:

  • Vectors (one-dimensional arrays)
  • Multi-dimensional arrays
  • Specialized arrays
  • Adjustable arrays
  • Strings

Because my goal is to cover the essentials for getting started with Common Lisp and arrays are largely for optimization, we won't go into great detail about arrays. However, I will talk about vectors.

Making a vector

Vectors are simple one-dimensional arrays.

make-array is the primary function for making arrays of all types.

(make-array 5)

The first argument to make-array is the dimensions of the array: how many columns in how many rows. A single number makes a one-dimension array with the same number of elements as the number.

You can set the initial element(s) in the array:

(make-array 5 :initial-element "ALMIGHTY")
Result#("ALMIGHTY" "ALMIGHTY" "ALMIGHTY" "ALMIGHTY" "ALMIGHTY")
(make-array 5 :initial-contents '(this is almighty common lisp))
Result#(THIS IS ALMIGHTY COMMON LISP)

You can also use the # reader macro.

(defparameter *almighty* #(this is almighty common lisp))

Accessing data in a vector

Use aref to access the data by index.

(aref *almighty* 0)
ResultTHIS
(aref *almighty* 4)
ResultLISP
(aref *almighty* 5)
ResultInvalid index 5 for (SIMPLE-VECTOR 5), should be a non-negative integer below 5.
   [Condition of type SB-INT:INVALID-ARRAY-INDEX-ERROR]

This shows one of the first important differences between a list and an array in practical use. Using nth to get an element in a list by its place in the list, if you go beyond the last element, nth only returns NIL without an error.

(defparameter *almighty-list* '(this is almighty common lisp))
(nth 0 *almighty-list*)
ResultTHIS
(nth 4 *almighty-list*)
ResultLISP
(nth 99 *almighty-list*)
ResultNIL

Modifying data in a vector

Use setf to modify the data at a place in a vector

(aref *almighty* 3)
(setf (aref *almighty* 3) 'powerful)
*almighty*
Result#(THIS IS ALMIGHTY POWERFUL LISP)

Adding/Deleting an element of a vector

Deleting items from an array requires more manual work than with lists.

One method involves changing the size of the vector with adjust-array, leaving no "empty" cells. This method relies on the array being created with the :adjustable option set to t, making it an ~adjustable array=.

(defparameter *adjustably-almighty* (make-array 5 :initial-contents '(THIS IS ALMIGHTY COMMON LISP) :adjustable t))

(defun inefficiently-delete-vector-element (vector index)
  (when (< index (length vector))
    (loop for i from index below (1- (length vector))
          ;; Move all elements above the target index down one cell,
          do (setf (aref vector i) (aref vector (1+ i))))
    (adjust-array vector (1- (length vector))))  ; Resize the array.
  vector)

Now we can use it:

(inefficiently-delete-vector-element *adjustably-almighty* 3)
Result#(THIS IS ALMIGHTY LISP)

You can set a :fill-pointer for the array:

(defparameter *fill-pointer-almighty* (make-array 5 :initial-contents '(this is almighty common lisp) :fill-pointer 5))

Use vector-pop to remove the last element of a vector that has a :fill-pointer set. Use vector-push to add an element to the end of the vector. It will not change the size/dimensions of the array.

(vector-pop *fill-pointer-almighty*)
ResultLISP

The data is still there if we look:

(aref *fill-pointer-almighty* 4)
ResultLISP

And the size of the array hasn't changed:

(array-dimensions *fill-pointer-almighty*)
Result(5)

But if we look at it's contents…

(loop :for word :across *fill-pointer-almighty*
      :collect word)
Result(THIS IS ALMIGHTY COMMON)

…we only show contents up to the fill-pointer.

But we can add content back:

(vector-push 'java *fill-pointer-almighty*)
Result4
(print *fill-pointer-almighty*)
Result#(THIS IS ALMIGHTY COMMON JAVA)

And we can check data at the previous position:

(aref *fill-pointer-almighty* 4)
ResultJAVA

vector-push won't resize the array beyond the dimensions defined with make-array. Use vector-push-extend to resize the array when it's necessary.

(vector-push 'dude *fill-pointer-almighty*)
ResultNIL

There's no change yet because we can't add anything past the fill pointer.

(print *fill-pointer-almighty*)
Result#(THIS IS ALMIGHTY COMMON JAVA)

And the array hasn't changed size:

(array-dimensions *fill-pointer-almighty*)
Result(5)
(vector-push-extend 'dude *fill-pointer-almighty*)
Result5
(print *fill-pointer-almighty*)
Result#(THIS IS ALMIGHTY COMMON JAVA DUDE)

To what extent the array is enlarged is implementation dependent.

(array-dimensions *fill-pointer-almighty*)
Result(10)

You can optionally specify how much to extend the array:

(vector-push-extend 'what *fill-pointer-almighty*)
(vector-push-extend 'in *fill-pointer-almighty*)
(vector-push-extend 'the *fill-pointer-almighty*)
(vector-push-extend 'world *fill-pointer-almighty*)
(vector-push-extend 'is *fill-pointer-almighty*)

(vector-push-extend 'this *fill-pointer-almighty* 1)
(array-dimensions *fill-pointer-almighty*)
(12)

Deleting an element in the middle of a non-adjustable vector requires shifting elements that come after it down one cell and then setting the last element in the array to NIL.

(defparameter *almighty* (make-array 5 :initial-contents '(THIS IS ALMIGHTY COMMON LISP)))
(defun delete-vector-element (vector index)
  (when (< index (length vector))
    (loop for i from index below (1- (length vector))
          do (setf (aref vector i) (aref vector (1+ i))))
    (setf (aref vector (1- (length vector))) nil))  ; Clear last element
  vector)

Calling it twice shows the progressive effect:

(delete-vector-element *almighty* 3)
(delete-vector-element *almighty* 3)
Result#(THIS IS ALMIGHTY LISP NIL)
#(THIS IS ALMIGHTY NIL NIL)

This method leaves the array dimensions untouched, shifts all elements above the given index down one cell in the array, and leaves NIL to fill the empty space.

STRUCTURES

Structures are user-defined data types that group related data. They are similar to classes, but are faster and more memory efficient than classes under some scenarios.

Defining structures and making instances

Use defstruct to define a structure. After giving the structure a name, define the slots. Use make-<structure name> to make an instance of the structure. Assign slot values at instantiation using keywords.

(defstruct s-point
  x
  y)

(defstruct http-request
  (status 200)
  (headers '(()))
  (body nil))

Making instances with and without keyword arguments:

(make-s-point)
(make-http-request)
(make-http-request :status 404 :body "This page doesn't exist.")
Result#S(S-POINT :X NIL :Y NIL)
#S(HTTP-REQUEST :STATUS 200 :HEADERS (NIL) :BODY NIL)
#S(HTTP-REQUEST :STATUS 404 :HEADERS (NIL) :BODY "This page doesn't exist.")

Slots default to NIL, but you can define defaults as in the http-request example. status defaults to 200, headers to '(()), etc.

If you define the :constructor for the structure you can make slots required.

(defstruct (animal (:constructor raise-animal (name &optional territory)))
  (name "")
  (territory "World"))

Calling with no arguments raises an error; providing a name works:

(raise-animal)
(raise-animal "Tiger")
ResultError: invalid number of arguments: 0
#S(ANIMAL :NAME "Tiger" :TERRITORY "World")

You can also define types–checked at compile time–for slots.

(defstruct s-point
  (x 0.0 :type float)
  (y 0.0 :type float))

Making instances:

(make-s-point)
(make-s-point :x 5)
Result#S(S-POINT :X 0.0 :Y 0.0)
The value 5 is not of type FLOAT when setting slot X of structure S-POINT
  [Condition of type TYPE-ERROR]

Structures support single inheritance.

(defstruct vehicle
  make
  model)

(defstruct (van (:include vehicle))
  (doors 4))

Making an instance of the child structure:

(make-van)
Result#S(VAN :MAKE NIL :MODEL NIL :DOORS 4)

Accessing/Modifying slots in a structure instance

To access slot values, pass the structure to the function called <structure-name>-<slot-name>, created when compiling the structure definition. Use setf to modify the value.

(defstruct s-point
  (x 0.0 :type float)
  (y 0.0 :type float))
(defparameter *s-point* (make-s-point :x 5.0 :y 10.0))
(s-point-x *s-point*)
Result5.0
(setf (s-point-x *s-point*) 10.0)
Result10.0
(s-point-x *s-point*)
Result10.0

Printing structures

Unlike classes or hash-tables, structures print in a somewhat human-readable form.

(print *s-point*)
Result#S(S-POINT :X 10.0 :Y 10.0)

Use the :print-structure option to customize how it is printed.

(defstruct (s-point (:print-function
                     (lambda (this stream depth)
                       (format stream "~&X: ~a Y: ~a~%" (s-point-x this) (s-point-y this)))))
  (x 0.0 :type float)
  (y 0.0 :type float))

Now print uses the custom format:

(print (make-s-point :x 5.0 :y 10.0))
ResultX: 5.0 Y: 10.0

The depth argument in the lambda list is required. When necessary, it is used to determine how to print nested structures.

Structures Vs. Classes

Structures and classes in Common Lisp are very similar. Given how their apparent use cases overlap, why would you choose one over the other?

For the purposes of comparison, let's set up a structure and a class that model a point on a 2D plane.

(defstruct s-point
  x
  y)
(defparameter *s-point* (make-s-point :x 5 :y 10))

(defclass c-point ()
  ((x :initarg :x
      :initform nil
      :accessor c-point-x)
   (y :initarg :y
      :initform nil
      :accessor c-point-y)))
(defparameter *c-point* (make-instance 'c-point :x 5 :y 10))

Structures and classes are both user-defined data types, they both let you define groups of related data into slots, and those slots can include default values and other options.

Generic methods can be specialized on both structures and classes.

(defgeneric add (point-a point-b))

(defmethod add ((a s-point) (b s-point))
  (make-s-point :x (+ (s-point-x a) (s-point-x b))
                :y (+ (s-point-y a) (s-point-y b))))

(defmethod add ((a c-point) (b c-point))
  (make-instance 'c-point
                 :x (+ (c-point-x a) (c-point-x b))
                 :y (+ (c-point-y a) (c-point-y b))))

(defmethod print-object ((this c-point) stream)
  (print-unreadable-object (this stream :type t :identity t)
    (format stream "~a ~a" (slot-value this 'x) (slot-value this 'y))))

Calling add on both types:

(add *s-point* *s-point*)             
Result#S(S-POINT :X 10 :Y 20)
(add *c-point* *c-point*)            
Result#<C-POINT 10 20 {700AF21593}>

However, similarities between structures and classes are superficial–the differences are much more significant.

Probably the most important difference between structures and classes in Lisp is that when you modify their definition, currently instantiated structures aren't updated with the new definition, but object instances are. This is called dynamic redefinition.

Accessing slots on both:

(s-point-x *s-point*)                  ; => 5
(c-point-x *c-point*)                  ; => 5

Now, what happens to instances if we change their definitions?

With the structure:

(defstruct s-point
  id
  x
  y)
Resultattempt to redefine the STRUCTURE-OBJECT class S-POINT
incompatibly with the current definition
   [Condition of type SIMPLE-ERROR]
Restarts:
 0: [CONTINUE] Use the new definition of S-POINT, invalidating already-loaded code and instances.
 1: [RECKLESSLY-CONTINUE] Use the new definition of S-POINT as if it were compatible, allowing old accessors to use new instances and allowing new accessors to use old instances.
 2: [RETRY] Retry SLY evaluation request.
 3: [*ABORT] Return to SLY's top level.
 4: [ABORT] abort thread (#<THREAD tid=21363 "slynk-worker" RUNNING {700B52AE53}>)

We need to overwrite the current definition and invalidate loaded code and instances, or RECKLESSLY-CONTINUE.

With the class, adding an id slot compiles without trouble.

(defclass c-point ()
  ((id :initarg :id
       :initform 7
       :accessor id)
   (x :initarg :x
      :initform nil
      :accessor x)
   (y :initarg :y
      :initform nil
      :accessor y)))

(id *c-point*)
Result7

The existing instance is given the default initform. Just to repeat: a slot was added to an object instance simply by redefining and recompiling the class.

We can remove the id slot again:

(defclass c-point ()
  ((x :initarg :x
      :initform nil
      :accessor x)
   (y :initarg :y
      :initform nil
      :accessor y)))

(id *c-point*)
ResultThere is no applicable method for the generic function
  #<STANDARD-GENERIC-FUNCTION BOOK/ROUTES::ID (0)>
when called with arguments
  (#<C-POINT 5 10 {700D71E033}>).
   [Condition of type SB-PCL::NO-APPLICABLE-METHOD-ERROR]
(slot-value *c-point* 'id)
ResultWhen attempting to read the slot's value (slot-value), the slot
ID is missing from the object #<C-POINT 5 10 {700D71E033}>.
   [Condition of type SB-PCL::MISSING-SLOT]

The slot and it's accessor are just gone. From the object instance.

So when you are actively developing and modifying a running program, classes provide far more flexibility.

Both structures and classes support inheritance, but classes support multiple inheritance.

Structures can modify how print will display data using :print-function, but classes need to use the print-object generic method.

(defstruct (s-point (:print-function
                     (lambda (this stream depth)
                       (format stream "~&X: ~a Y: ~a~%" (s-point-x this) (s-point-y this)))))
  (x 0.0 :type float)
  (y 0.0 :type float))

(defmethod print-object ((this c-point) stream)
  (print-unreadable-object (this stream :type t :identity t)
    (format stream "~a ~a" (slot-value this 'x) (slot-value this 'y))))

When you define slot types with structures, Lisp will check the type of value passed to it at compile time. Class slot type definitions aren't enforced–you need to enforce them yourself with initialize-instance :before.

(defclass c-point ()
  ((x :initarg :x
      :initform 0.0
      :accessor c-point-x
      :type float)                      ; added type
   (y :initarg :y
      :initform 0.0
      :accessor c-point-y
      :type float)))                    ; added type

The type declaration alone does not prevent invalid values (in SBCL):

(make-instance 'c-point :x "five dot zero")
Result#<C-POINT five dot zero 0.0 {700C9969C3}>

Use initialize-instance :before to enforce types:

(defmethod initialize-instance :before ((p c-point) &key x y)
  (unless (typep x 'float)
    (error 'type-error :datum x :expected-type 'float))
  (unless (typep y 'float)
    (error 'type-error :datum y :expected-type 'float)))

(make-instance 'c-point :x "invalid" :y 2.0)
ResultThe value
  "invalid"
is not of type
  FLOAT
   [Condition of type TYPE-ERROR]

Type definitions in class slots should be considered "documentation"–you need to enforce the types yourself. With structure slots, types are enforced by the compiler.

So which one do you choose? Generally, structures are going to be most useful for optimizing speed and memory efficiency. Classes are otherwise superior due to their more dynamic, flexible design. Unless you know for certain that you need an optimized data structure, stick with classes.