LISP - Accessors and Mutators
Modern Lisp: User-Defined Structures with defstruct
Section titled “Modern Lisp: User-Defined Structures with defstruct”In Lisp, defstruct is the standard way to define new, lightweight, user-defined data types. A structure is an aggregate of named slots, similar to a struct in C or a simple class in Python. For each structure you define, Common Lisp automatically generates a set of useful functions, including constructors, accessors, and predicates, which provide a clean and controlled interface for interacting with your data.
Defining a Structure and its Accessors
Section titled “Defining a Structure and its Accessors”When you define a structure with defstruct, Lisp automatically creates accessor functions to retrieve the value from each slot. These functions are named by concatenating the structure name and the slot name.
Example: Defining a book Structure
Section titled “Example: Defining a book Structure”Let’s define a book structure with slots for title, author, and publication year. We can also provide default values and type hints.
(defstruct book (title "" :type string) (author "" :type string) (publication-year 0 :type (integer 0 *)))
;; By defining this structure, Lisp automatically provides:;; - A constructor: make-book;; - Accessors: book-title, book-author, book-publication-year;; - A copier: copy-book;; - A predicate: book-pWe can now create an instance of our book and use the auto-generated accessors to read its slot values.
;; Create an instance of the book structure(defparameter *my-book* (make-book :title "Structure and Interpretation of Computer Programs" :author "Abelson & Sussman" :publication-year 1985))
;; Use the accessor to get the title(book-title *my-book*) ; => "Structure and Interpretation of Computer Programs"Mutators: Modifying Structure Slots
Section titled “Mutators: Modifying Structure Slots”To modify a value in a structure’s slot, Lisp uses a powerful and general-purpose macro: setf. You use setf in combination with the slot’s accessor function to specify the “place” you want to modify. This is the idiomatic and universal way to handle mutation in Common Lisp.
Example: A Complete Workflow
Section titled “Example: A Complete Workflow”The following complete example demonstrates defining, creating, accessing, and modifying a structure.
main.lisp
Section titled “main.lisp”;; Define a structure for a 2D point(defstruct point (x 0.0 :type float) (y 0.0 :type float))
;; Create an instance representing the origin(defparameter *p1* (make-point))
;; Print the initial y-coordinate(format t "Initial Y: ~a~%" (point-y *p1*))
;; Mutate the y-coordinate using SETF and the accessor(setf (point-y *p1*) 10.5)
;; Print the updated y-coordinate(format t "Updated Y: ~a~%" (point-y *p1*))
;; The book-p predicate checks if an object is of type book(format t "Is *p1* a point? ~a~%" (point-p *p1*))(format t "Is 42 a point? ~a~%" (point-p 42))Output
Section titled “Output”When you compile and load this file, you’ll see:
Initial Y: 0.0Updated Y: 10.5Is *p1* a point? TIs 42 a point? NILKey Considerations and Best Practices
Section titled “Key Considerations and Best Practices”- Encapsulation: While not as strict as in some languages, using accessor functions with
setfprovides a controlled interface to your data, improving code clarity. - Structs vs. Classes:
defstructis ideal for simple data aggregates. For more complex behavior involving inheritance, methods, and polymorphism, you should use the Common Lisp Object System (CLOS) withdefclass. - Immutability: For a more functional style, consider treating your structs as immutable. Instead of modifying a struct, create a new one with the desired changes. The auto-generated
copy-bookfunction can be helpful here.