Next: Assertions and Errors, Previous: Lists, Up: GNU Emacs Common Lisp Emulation [Contents][Index]
The Common Lisp structure mechanism provides a general way
to define data types similar to C’s struct types. A
structure is a Lisp object containing some number of slots,
each of which can hold any Lisp data object.
You can create a new structure with the cl-defstruct macro. This
macro automatically generates functions to access and modify its slots,
create or copy structure objects, and to test whether an object belongs
to the defined structure type.
In standard Common Lisp, each structure type is a new type distinct from all existing Lisp types. However, because Emacs Lisp lacks native support for defining new distinct types, this package implements structures using vectors (or lists upon request) with a special “tag” symbol that to identify them.
The cl-defstruct form defines a new structure type called
name, with the specified slots. (The slots
may begin with a string which documents the structure type.)
In the simplest case, name and each of the slots
are symbols. For example, this is how you define a struct type called
person that contains three slots:
(cl-defstruct person first-name age sex)
The cl-defstruct macro creates a new constructor function, such
as make-person in this example, which returns a new struct
instance. This constructor accepts keyword arguments that correspond to
the specified slots, such as :first-name, :age, and
:sex. These keyword arguments specify the initial values for the
respective slots in the new object. If a keyword argument is not
provided, the slot is initialized to nil.1
In the example below, we create a new instance of the person
struct, and store it in the variable birthday-boy for later use:
(setq birthday-boy (make-person :first-name "Peter" :age 23 :sex "male")) ⇒ #s(person "Peter" 23 "male")
Given a person object p, you can access those slots by
calling (person-first-name p), (person-age p),
and (person-sex p).
You can also update the values of these slots using setf on any
of these place forms, for example:
(setf (person-first-name birthday-boy) "Old Peter") ⇒ "Old Peter" birthday-boy ⇒ #s(person "Old Peter" 23 "male")
Any macro that accepts a generalized variable can be used to modify
struct fields (see Generalized Variables in GNU Emacs Lisp
Reference Manual). Here is an example using incf:
(incf (person-age birthday-boy)) ⇒ 24 birthday-boy ⇒ #s(person "Old Peter" 24 "male")
Given a person, (copy-person p) makes a new
object of the same type whose slots are eq to those of p.
Given any Lisp object x, (person-p x) returns
true if x is a person, and false otherwise.
Accessors like person-first-name normally check their arguments
(effectively using person-p) and signal an error if the
argument is the wrong type. This check is affected by
(optimize (safety …)) declarations. Safety level 1,
the default, uses a somewhat optimized check that will detect all
incorrect arguments, but may use an uninformative error message
(e.g., “expected a vector” instead of “expected a person”).
Safety level 0 omits all checks except as provided by the underlying
aref call; safety levels 2 and 3 do rigorous checking that will
always print a descriptive error message for incorrect inputs.
See Declarations.
(setq dave (make-person :first-name "Dave" :sex 'male)) ⇒ [cl-struct-person "Dave" nil male] (setq other (copy-person dave)) ⇒ [cl-struct-person "Dave" nil male] (eq dave other) ⇒ nil (eq (person-first-name dave) (person-first-name other)) ⇒ t (person-p dave) ⇒ t (person-p [1 2 3 4]) ⇒ nil (person-p "Bogus") ⇒ nil (person-p '[cl-struct-person counterfeit person object]) ⇒ t
In general, name is either a name symbol or a list of a name symbol followed by any number of structure options; each slot is either a slot symbol or a list of the form ‘(slot-name default-value slot-options…)’. The default-value is a Lisp form that is evaluated any time an instance of the structure type is created without specifying that slot’s value.
(cl-defstruct person (first-name nil :read-only t) age (sex 'unknown))
slot-options is a list of keyword-value pairs, where the following keywords can be used:
:read-onlyA non-nil value means the slot should not be setf-able;
the slot’s value is determined when the object is created and does
not change afterward.
:typeThe expected type of the values held in this slot.
:documentationA documentation string describing the slot.
Other slot options are currently ignored.
You can use cl-with-accessors to lexically define symbols as
expressions calling the given accessor functions on a single instance of
a structure or class defined by cl-defstruct or defclass
(see Building Classes in EIEIO). This can simplify code that
repeatedly accesses slots. With it, you can use setf and
setq on the symbols like normal variables, modifying the values
in the structure. Unlike the macro with-slots (see Accessing
Slots in EIEIO), because the symbol expands to a function call,
cl-with-accessors can be used with any generalized variable that
can take a single argument, such as cl-first and cl-rest.
;; Using accessors with long, clear names without the macro: (defun internal-normalization (person) "Correct the values of the slots in PERSON to be as expected." ;; Check the values of the structure: (when (equal (person-optional-secondary-data person) "") (setf (person-optional-secondary-data person) nil)) (when (null (person-access-settings person)) (setf (person-access-settings person) 'default)) (when (< (long-accessor-name-that-can-become-unreadable-when-repeated person) 9) (cl-incf (long-accessor-name-that-can-become-unreadable-when-repeated person) 100)) ;; And so on before returning the structure: person) ;; Using accessors with long, clear names with the macro: (defun internal-normalization (person) "Correct the values of the slots in PERSON to be as expected." (cl-with-accessors ((secondary-data person-optional-secondary-data) (access-settings person-access-settings) (short-name person-much-longer-accessor-name)) person ;; Check the values of the structure: (when (equal secondary-data "") (setf secondary-data nil)) (when (null access-settings) (setf access-settings 'default)) (when (< short-name 9) (cl-incf short-name 100)) ;; And so on before returning the structure: person))
For obscure historical reasons, structure options take a different form than slot options. A structure option is either a keyword symbol, or a list beginning with a keyword symbol possibly followed by arguments. (By contrast, slot options are key-value pairs not enclosed in lists.)
(cl-defstruct (person (:constructor create-person) (:type list) :named) first-name age sex)
The following structure options are recognized.
:conc-nameThe argument is a symbol whose print name is used as the prefix for
the names of slot accessor functions. The default is the name of
the struct type followed by a hyphen. The option (:conc-name p-)
would change this prefix to p-. Specifying nil as an
argument means no prefix, so that the slot names themselves are used
to name the accessor functions.
:constructorIn the simple case, this option takes one argument which is an
alternate name to use for the constructor function. The default
is make-name, e.g., make-person. The above
example changes this to create-person. Specifying nil
as an argument means that no standard constructor should be
generated at all.
In the full form of this option, the constructor name is followed
by an arbitrary argument list. See Program Structure, for a
description of the format of Common Lisp argument lists. All
options, such as &rest and &key, are supported.
The argument names should match the slot names; each slot is
initialized from the corresponding argument. Slots whose names
do not appear in the argument list are initialized based on the
default-value in their slot descriptor. Also, &optional
and &key arguments that don’t specify defaults take their
defaults from the slot descriptor. It is valid to include arguments
that don’t correspond to slot names; these are useful if they are
referred to in the defaults for optional, keyword, or &aux
arguments that do correspond to slots.
You can specify any number of full-format :constructor
options on a structure. The default constructor is still generated
as well unless you disable it with a simple-format :constructor
option.
(cl-defstruct (person (:constructor nil) ; no default constructor (:constructor new-person (first-name sex &optional (age 0))) (:constructor new-hound (&key (first-name "Rover") (dog-years 0) &aux (age (* 7 dog-years)) (sex 'canine)))) first-name age sex)
The first constructor here takes its arguments positionally rather
than by keyword. (In official Common Lisp terminology, constructors
that work By Order of Arguments instead of by keyword are called
“BOA constructors”. No, I’m not making this up.) For example,
(new-person "Jane" 'female) generates a person whose slots
are "Jane", 0, and female, respectively.
The second constructor takes two keyword arguments, :name,
which initializes the name slot and defaults to "Rover",
and :dog-years, which does not itself correspond to a slot
but which is used to initialize the age slot. The sex
slot is forced to the symbol canine with no syntax for
overriding it.
:copierThe argument is an alternate name for the copier function for
this type. The default is copy-name. nil
means not to generate a copier function. (In this implementation,
all copier functions are simply synonyms for copy-sequence.)
:predicateThe argument is an alternate name for the predicate that recognizes
objects of this type. The default is name-p. nil
means not to generate a predicate function. (If the :type
option is used without the :named option, no predicate is
ever generated.)
In true Common Lisp, typep is always able to recognize a
structure object even if :predicate was used. In this
package, cl-typep simply looks for a function called
typename-p, so it will work for structure types
only if they used the default predicate name.
:includeThis option implements a very limited form of C++-style inheritance.
The argument is the name of another structure type previously
created with cl-defstruct. The effect is to cause the new
structure type to inherit all of the included structure’s slots
(plus, of course, any new slots described by this struct’s slot
descriptors). The new structure is considered a “specialization”
of the included one. In fact, the predicate and slot accessors
for the included type will also accept objects of the new type.
If there are extra arguments to the :include option after
the included-structure name, these options are treated as replacement
slot descriptors for slots in the included structure, possibly with
modified default values. Borrowing an example from Steele:
(cl-defstruct person first-name (age 0) sex) ⇒ person (cl-defstruct (astronaut (:include person (age 45))) helmet-size (favorite-beverage 'tang)) ⇒ astronaut (setq joe (make-person :first-name "Joe")) ⇒ [cl-struct-person "Joe" 0 nil] (setq buzz (make-astronaut :first-name "Buzz")) ⇒ [cl-struct-astronaut "Buzz" 45 nil nil tang] (list (person-p joe) (person-p buzz)) ⇒ (t t) (list (astronaut-p joe) (astronaut-p buzz)) ⇒ (nil t) (person-first-name buzz) ⇒ "Buzz" (astronaut-first-name joe) ⇒ error: "astronaut-first-name accessing a non-astronaut"
Thus, if astronaut is a specialization of person,
then every astronaut is also a person (but not the
other way around). Every astronaut includes all the slots
of a person, plus extra slots that are specific to
astronauts. Operations that work on people (like person-first-name)
work on astronauts just like other people.
:noinlineIf this option is present, this structure’s functions will not be inlined, even functions that normally would.
:print-functionIn full Common Lisp, this option allows you to specify a function
that is called to print an instance of the structure type. The
Emacs Lisp system offers no hooks into the Lisp printer which would
allow for such a feature, so this package simply ignores
:print-function.
:typeThe argument should be one of the symbols vector or
list. This tells which underlying Lisp data type should be
used to implement the new structure type. Records are used by
default, but (:type vector) will cause structure objects to be
stored as vectors and (:type list) lists instead.
The record and vector representations for structure objects have the advantage that all structure slots can be accessed quickly, although creating them are a bit slower in Emacs Lisp. Lists are easier to create, but take a relatively long time accessing the later slots.
:namedThis option, which takes no arguments, causes a characteristic “tag”
symbol to be stored at the front of the structure object. Using
:type without also using :named will result in a
structure type stored as plain vectors or lists with no identifying
features.
The default, if you don’t specify :type explicitly, is to use
records, which are always tagged. Therefore, :named is only
useful in conjunction with :type.
(cl-defstruct (person1) first-name age sex) (cl-defstruct (person2 (:type list) :named) first-name age sex) (cl-defstruct (person3 (:type list)) first-name age sex) (cl-defstruct (person4 (:type vector)) first-name age sex) (setq p1 (make-person1)) ⇒ #s(person1 nil nil nil) (setq p2 (make-person2)) ⇒ (person2 nil nil nil) (setq p3 (make-person3)) ⇒ (nil nil nil) (setq p4 (make-person4)) ⇒ [nil nil nil] (person1-p p1) ⇒ t (person2-p p2) ⇒ t (person3-p p3) ⇒ error: function person3-p undefined
Since unnamed structures don’t have tags, cl-defstruct is not
able to make a useful predicate for recognizing them. Also,
accessors like person3-first-name will be generated but they
will not be able to do any type checking. The person3-first-name
function, for example, will simply be a synonym for car in
this case. By contrast, person2-first-name is able to verify
that its argument is indeed a person2 object before
proceeding.
:initial-offsetThe argument must be a nonnegative integer. It specifies a
number of slots to be left “empty” at the front of the
structure. If the structure is named, the tag appears at the
specified position in the list or vector; otherwise, the first
slot appears at that position. Earlier positions are filled
with nil by the constructors and ignored otherwise. If
the type :includes another type, then :initial-offset
specifies a number of slots to be skipped between the last slot
of the included type and the first new slot.
Except as noted, the cl-defstruct facility of this package is
entirely compatible with that of Common Lisp.
The cl-defstruct package also provides a few structure
introspection functions.
This function returns the underlying data structure for
struct-type, which is a symbol. It returns record,
vector or list, or nil if struct-type is
not actually a structure.
This function returns a list of slot descriptors for structure
struct-type. Each entry in the list is (name . opts),
where name is the name of the slot and opts is the list
of slot options given to defstruct. Dummy entries represent
the slots used for the struct name and that are skipped to implement
:initial-offset.
Return the offset of slot slot-name in struct-type. The
returned zero-based slot index is relative to the start of the
structure data type and is adjusted for any structure name and
:initial-offset slots. Signal error if struct struct-type does
not contain slot-name.
Return the value of slot slot-name in inst of
struct-type. struct and slot-name are symbols.
inst is a structure instance. This routine is also a
setf place. Can signal the same errors as cl-struct-slot-offset.
This behavior differs from Common Lisp, where an uninitialized slot would be left as “undefined”.
Next: Assertions and Errors, Previous: Lists, Up: GNU Emacs Common Lisp Emulation [Contents][Index]