LISP - Structure Options
Lisp - Customizing Structures with defstruct Options
Section titled “Lisp - Customizing Structures with defstruct Options”The defstruct macro is a powerful tool for creating custom data structures. Beyond defining simple slots, it offers a variety of options to control a structure’s behavior, such as how it’s created, printed, and identified.
The :constructor Option
Section titled “The :constructor Option”By default, defstruct creates a constructor function named make-<struct-name>. The :constructor option lets you customize this.
(:constructor): Suppresses the creation of any constructor.(:constructor make-my-point): Renames the constructor tomake-my-point.(:constructor make-point (x y)): Creates a constructor that accepts arguments positionally, not by keyword. This is useful for creating concise constructors for small, frequently used structs.
Example: A Positional Constructor
Section titled “Example: A Positional Constructor”;;; Define a point structure with a custom positional constructor(defstruct (point (:constructor make-point (x y))) x y)
;; Create an instance using the custom constructor(let ((p1 (make-point 10 20))) (format t "Created point with X=~d, Y=~d~%" (point-x p1) (point-y p1)))Output
Section titled “Output”Created point with X=10, Y=20The :predicate and :copier Options
Section titled “The :predicate and :copier Options”These options control the naming of the type-checking predicate and the structure copier function.
:predicate: The default predicate is<struct-name>-p. You can rename it with(:predicate is-a-point). Use(:predicate)to disable it.:copier: The default copier iscopy-<struct-name>. You can rename it with(:copier duplicate-point). Use(:copier)to disable it.
Example: Custom Predicate and Copier
Section titled “Example: Custom Predicate and Copier”(defstruct (user (:predicate userp) (:copier nil))) name id)
(let ((u1 (make-user :name "Alice" :id 101))) (format t "Is u1 a user? ~a~%" (userp u1)) ;; (copy-user u1) would cause an error because the copier was disabled.The :print-object Option
Section titled “The :print-object Option”The :print-object option (a more modern and user-friendly alternative to :print-function) allows you to define a custom function for how instances of your structure are displayed by print, format, etc. This is invaluable for debugging and creating readable output.
Example: A Custom Printer
Section titled “Example: A Custom Printer”;; First, define the function that will do the printing.;; It must accept two arguments: the object and the output stream.(defun print-custom-point (point stream) (format stream "<Point (~a, ~a)>" (point-x point) (point-y point)))
;; Now, define the structure and link it to our print function.(defstruct (point (:constructor make-point (x y)) (:print-object print-custom-point)) x y)
(let ((p1 (make-point 10 20))) ;; Now `print` and `format` will use our custom function automatically. (format t "The point is: ~a~%" p1))Output
Section titled “Output”The point is: <Point (10, 20)>The :include Option for Inheritance
Section titled “The :include Option for Inheritance”The :include option provides simple single inheritance, allowing one structure to inherit the slots of another. This is a great way to reduce code duplication.
Example: Inheriting Slots
Section titled “Example: Inheriting Slots”;; Base structure(defstruct shape (color "black"))
;; Circle inherits from shape and adds a radius(defstruct (circle (:include shape)) radius)
(let ((c1 (make-circle :radius 10 :color "red"))) ;; Accessors from both structs work (format t "Circle color: ~a, radius: ~a~%" (circle-color c1) (circle-radius c1)) (format t "The circle is: ~s~%" c1))Output
Section titled “Output”Circle color: red, radius: 10The circle is: #S(CIRCLE :COLOR "red" :RADIUS 10)The :type Option
Section titled “The :type Option”By default, structures may be implemented as lists or in some implementation-specific way. The :type option lets you force the underlying representation to be a list or a vector. This can be useful for performance tuning or interfacing with other code.
Example: A Vector-Based Structure
Section titled “Example: A Vector-Based Structure”;; Define a point backed by a vector(defstruct (vec-point (:type vector)) x y)
(let ((vp1 (make-vec-point :x 5 :y 15))) (format t "Type of vector-point: ~a~%" (type-of vp1)))Output
Section titled “Output”The output will be a vector type, which might look like this (exact output may vary by implementation):
Type of vector-point: (SIMPLE-VECTOR 2)Practical Advice
Section titled “Practical Advice”Combining these options allows you to create highly specialized and robust data types. For complex object-oriented programming with multiple inheritance and method dispatch, consider using the Common Lisp Object System (CLOS) with defclass, which defstruct builds upon.