Next: , Previous: Iteration, Up: Special Forms


2.10 Structure Definitions

This section provides examples and describes the options and syntax of define-structure, an MIT/GNU Scheme macro that is very similar to defstruct in Common Lisp. The differences between them are summarized at the end of this section. For more information, see Steele's Common Lisp book.

— special form: define-structure (name structure-option ...) slot-description ...

Each slot-description takes one of the following forms:

          slot-name
          (slot-name default-init [slot-option value]*)

The fields name and slot-name must both be symbols. The field default-init is an expression for the initial value of the slot. It is evaluated each time a new instance is constructed. If it is not specified, the initial content of the slot is undefined. Default values are only useful with a boa constructor with argument list or a keyword constructor (see below).

Evaluation of a define-structure expression defines a structure descriptor and a set of procedures to manipulate instances of the structure. These instances are represented as records by default (see Records) but may alternately be lists or vectors. The accessors and modifiers are marked with compiler declarations so that calls to them are automatically transformed into appropriate references. Often, no options are required, so a simple call to define-structure looks like:

          (define-structure foo a b c)

This defines a type descriptor rtd:foo, a constructor make-foo, a predicate foo?, accessors foo-a, foo-b, and foo-c, and modifiers set-foo-a!, set-foo-b!, and set-foo-c!.

In general, if no options are specified, define-structure defines the following (using the simple call above as an example):

type descriptor
The name of the type descriptor is "rtd:" followed by the name of the structure, e.g. ‘rtd:foo’. The type descriptor satisfies the predicate record-type?.
constructor
The name of the constructor is "make-" followed by the name of the structure, e.g. ‘make-foo’. The number of arguments accepted by the constructor is the same as the number of slots; the arguments are the initial values for the slots, and the order of the arguments matches the order of the slot definitions.
predicate
The name of the predicate is the name of the structure followed by "?", e.g. ‘foo?’. The predicate is a procedure of one argument, which returns #t if its argument is a record of the type defined by this structure definition, and #f otherwise.
accessors
For each slot, an accessor is defined. The name of the accessor is formed by appending the name of the structure, a hyphen, and the name of the slot, e.g. ‘foo-a’. The accessor is a procedure of one argument, which must be a record of the type defined by this structure definition. The accessor extracts the contents of the corresponding slot in that record and returns it.
modifiers
For each slot, a modifier is defined. The name of the modifier is formed by appending "set-", the name of the accessor, and "!", e.g. ‘set-foo-a!’. The modifier is a procedure of two arguments, the first of which must be a record of the type defined by this structure definition, and the second of which may be any object. The modifier modifies the contents of the corresponding slot in that record to be that object, and returns an unspecified value.

When options are not supplied, (name) may be abbreviated to name. This convention holds equally for structure-options and slot-options. Hence, these are equivalent:

          (define-structure foo a b c)
          (define-structure (foo) (a) b (c))

as are

          (define-structure (foo keyword-constructor) a b c)
          (define-structure (foo (keyword-constructor)) a b c)

When specified as option values, false and nil are equivalent to #f, and true and t are equivalent to #t.

Possible slot-options are:

— slot option: read-only value

When given a value other than #f, this specifies that no modifier should be created for the slot.

— slot option: type type-descriptor

This is accepted but not presently used.

Possible structure-options are:

— structure option: predicate [name]

This option controls the definition of a predicate procedure for the structure. If name is not given, the predicate is defined with the default name (see above). If name is #f, the predicate is not defined at all. Otherwise, name must be a symbol, and the predicate is defined with that symbol as its name.

— structure option: copier [name]

This option controls the definition of a procedure to copy instances of the structure. This is a procedure of one argument, a structure instance, that makes a newly allocated copy of the structure and returns it. If name is not given, the copier is defined, and the name of the copier is "copy-" followed by the structure name (e.g. ‘copy-foo’). If name is #f, the copier is not defined. Otherwise, name must be a symbol, and the copier is defined with that symbol as its name.

— structure option: print-procedure expression

Evaluating expression must yield a procedure of two arguments, which is used to print instances of the structure. The procedure is an unparser method (see Custom Output). If the structure instances are records, this option has the same effect as calling set-record-type-unparser-method!.

— structure option: constructor [name [argument-list]]

This option controls the definition of constructor procedures. These constructor procedures are called “boa constructors”, for “By Order of Arguments”, because the arguments to the constructor specify the initial contents of the structure's slots by the order in which they are given. This is as opposed to “keyword constructors”, which specify the initial contents using keywords, and in which the order of arguments is irrelevant.

If name is not given, a constructor is defined with the default name and arguments (see above). If name is #f, no constructor is defined; argument-list may not be specified in this case. Otherwise, name must be a symbol, and a constructor is defined with that symbol as its name. If name is a symbol, argument-list is optionally allowed; if it is omitted, the constructor accepts one argument for each slot in the structure definition, in the same order in which the slots appear in the definition. Otherwise, argument-list must be a lambda list (see Lambda Expressions), and each of the parameters of the lambda list must be the name of a slot in the structure. The arguments accepted by the constructor are defined by this lambda list. Any slot that is not specified by the lambda list is initialized to the default-init as specified above; likewise for any slot specified as an optional parameter when the corresponding argument is not supplied.

If the constructor option is specified, the default constructor is not defined. Additionally, the constructor option may be specified multiple times to define multiple constructors with different names and argument lists.

          (define-structure (foo
                             (constructor make-foo (#!optional a b)))
            (a 6 read-only #t)
            (b 9))
— structure option: keyword-constructor [name]

This option controls the definition of keyword constructor procedures. A keyword constructor is a procedure that accepts arguments that are alternating slot names and values. If name is omitted, a keyword constructor is defined, and the name of the constructor is "make-" followed by the name of the structure (e.g. ‘make-foo’). Otherwise, name must be a symbol, and a keyword constructor is defined with this symbol as its name.

If the keyword-constructor option is specified, the default constructor is not defined. Additionally, the keyword-constructor option may be specified multiple times to define multiple keyword constructors; this is usually not done since such constructors would all be equivalent.

          (define-structure (foo (keyword-constructor make-bar)) a b)
          (foo-a (make-bar 'b 20 'a 19))         ⇒ 19
— structure option: type-descriptor name

This option cannot be used with the type or named options.

By default, structures are implemented as records. The name of the structure is defined to hold the type descriptor of the record defined by the structure. The type-descriptor option specifies a different name to hold the type descriptor.

          (define-structure foo a b)
          foo             ⇒ #[record-type 18]
          
          (define-structure (bar (type-descriptor <bar>)) a b)
          bar             error--> Unbound variable: bar
          <bar>         ⇒ #[record-type 19]
— structure option: conc-name [name]

By default, the prefix for naming accessors and modifiers is the name of the structure followed by a hyphen. The conc-name option can be used to specify an alternative. If name is not given, the prefix is the name of the structure followed by a hyphen (the default). If name is #f, the slot names are used directly, without prefix. Otherwise, name must a symbol, and that symbol is used as the prefix.

          (define-structure (foo (conc-name moby/)) a b)

defines accessors moby/a and moby/b, and modifiers set-moby/a! and set-moby/b!.

          (define-structure (foo (conc-name #f)) a b)

defines accessors a and b, and modifiers set-a! and set-b!.

— structure option: type representation-type

This option cannot be used with the type-descriptor option.

By default, structures are implemented as records. The type option overrides this default, allowing the programmer to specify that the structure be implemented using another data type. The option value representation-type specifies the alternate data type; it is allowed to be one of the symbols vector or list, and the data type used is the one corresponding to the symbol.

If this option is given, and the named option is not specified, the representation will not be tagged, and neither a predicate nor a type descriptor will be defined; also, the print-procedure option may not be given.

          (define-structure (foo (type list)) a b)
          (make-foo 1 2)                          ⇒ (1 2)
— structure option: named [expression]

This is valid only in conjunction with the type option and specifies that the structure instances be tagged to make them identifiable as instances of this structure type. This option cannot be used with the type-descriptor option.

In the usual case, where expression is not given, the named option causes a type descriptor and predicate to be defined for the structure (recall that the type option without named suppresses their definition), and also defines a default unparser method for the structure instances (which can be overridden by the print-procedure option). If the default unparser method is not wanted then the print-procedure option should be specified as #F. This causes the structure to be printed in its native representation, as a list or vector, which includes the type descriptor. The type descriptor is a unique object, not a record type, that describes the structure instances and is additionally stored in the structure instances to identify them: if the representation type is vector, the type descriptor is stored in the zero-th slot of the vector, and if the representation type is list, it is stored as the first element of the list.

          (define-structure (foo (type vector) named) a b c)
          (vector-ref (make-foo 1 2 3) 0) ⇒ #[structure-type 52]

If expression is specified, it is an expression that is evaluated to yield a tag object. The expression is evaluated once when the structure definition is evaluated (to specify the unparser method), and again whenever a predicate or constructor is called. Because of this, expression is normally a variable reference or a constant. The value yielded by expression may be any object at all. That object is stored in the structure instances in the same place that the type descriptor is normally stored, as described above. If expression is specified, no type descriptor is defined, only a predicate.

          (define-structure (foo (type vector) (named 'foo)) a b c)
          (vector-ref (make-foo 1 2 3) 0) ⇒ foo
— structure option: safe-accessors [boolean]

This option allows the programmer to have some control over the safety of the slot accessors (and modifiers) generated by define-structure. If safe-accessors is not specified, or if boolean is #f, then the accessors are optimized for speed at the expense of safety; when compiled, the accessors will turn into very fast inline sequences, usually one to three machine instructions in length. However, if safe-accessors is specified and boolean is either omitted or #t, then the accessors are optimized for safety, will check the type and structure of their argument, and will be close-coded.

          (define-structure (foo safe-accessors) a b c)
— structure option: initial-offset offset

This is valid only in conjunction with the type option. Offset must be an exact non-negative integer and specifies the number of slots to leave open at the beginning of the structure instance before the specified slots are allocated. Specifying an offset of zero is equivalent to omitting the initial-offset option.

If the named option is specified, the structure tag appears in the first slot, followed by the “offset” slots, and then the regular slots. Otherwise, the “offset” slots come first, followed by the regular slots.

          (define-structure (foo (type vector) (initial-offset 3))
            a b c)
          (make-foo 1 2 3)                ⇒ #(() () () 1 2 3)

The essential differences between MIT/GNU Scheme's define-structure and Common Lisp's defstruct are: