Common Lisp the Language, 2nd Edition

Next: Structures of Explicitly Up: Structures Previous: Defstruct Options

# 19.6. By-Position Constructor Functions

If the `:constructor` option is given as `(`:constructor` name arglist)`, then instead of making a keyword-driven constructor function, `defstruct` defines a ``positional'' constructor function, taking arguments whose meaning is determined by the argument's position rather than by a keyword. The arglist is used to describe what the arguments to the constructor will be. In the simplest case something like `(`:constructor` make-foo (a b c))` defines `make-foo` to be a three-argument constructor function whose arguments are used to initialize the slots named `a`, `b`, and `c`.

In addition, the keywords `&optional`, `&rest`, and `&aux` are recognized in the argument list. They work in the way you might expect, but there are a few fine points worthy of explanation. Consider this example:

    (:constructor create-foo
            (a &optional b (c 'sea) &rest d &aux e (f 'eff)))

This defines `create-foo` to be a constructor of one or more arguments. The first argument is used to initialize the `a` slot. The second argument is used to initialize the `b` slot. If there isn't any second argument, then the default value given in the body of the `defstruct` (if given) is used instead. The third argument is used to initialize the `c` slot. If there isn't any third argument, then the symbol `sea` is used instead. Any arguments following the third argument are collected into a list and used to initialize the `d` slot. If there are three or fewer arguments, then `nil` is placed in the `d` slot. The `e` slot is not initialized; its initial value is undefined. Finally, the `f` slot is initialized to contain the symbol `eff`.

The actions taken in the `b` and `e` cases were carefully chosen to allow the user to specify all possible behaviors. Note that the `&aux` ``variables'' can be used to completely override the default initializations given in the body.

With this definition, one can write

    (create-foo 1 2)

instead of

    (make-foo :a 1 :b 2)

and of course `create-foo` provides defaulting different from that of `make-foo`.

It is permissible to use the `:constructor` option more than once, so that you can define several different constructor functions, each taking different parameters.

Because a constructor of this type operates By Order of Arguments, it is sometimes known as a BOA constructor.

X3J13 voted in January 1989 (DEFSTRUCT-CONSTRUCTOR-KEY-MIXTURE) to allow `&key` and `&allow-other-keys` in the parameter list of a ``positional'' constructor. The initialization of slots corresponding to keyword parameters is performed in the same manner as for `&optional` parameters. A variant of the example shown above illustrates this:

    (:constructor create-foo
            (a &optional b (c 'sea)
             &key p (q 'cue) ((:why y)) ((:you u) 'ewe)
             &aux e (f 'eff)))

The treatment of slots `a`, `b`, `c`, `e`, and `f` is the same as in the original example. In addition, if there is a `:p` keyword argument, it is used to initialize the `p` slot; if there isn't any `:p` keyword argument, then the default value given in the body of the `defstruct` (if given) is used instead. Similarly, if there is a `:q` keyword argument, it is used to initialize the `q` slot; if there isn't any `:q` keyword argument, then the symbol `cue` is used instead.

In order thoroughly to flog this presumably already dead horse, we further observe that if there is a `:why` keyword argument, it is used to initialize the `y` slot; otherwise the default value for slot `y` is used instead. Similarly, if there is a `:you` keyword argument, it is used to initialize the `u` slot; otherwise the symbol `ewe` is used instead.

If memory serves me correctly, `defstruct` was included in the original design for Common Lisp some time before keyword arguments were approved. The failure of positional constructors to accept keyword arguments may well have been an oversight on my part; there is no logical reason to exclude them. I am grateful to X3J13 for rectifying this.

A remaining difficulty is that the possibility of keyword arguments renders the term ``positional constructor'' a misnomer. Worse yet, it ruins the term ``BOA constructor.'' I suggest that they continue to be called BOA constructors, as I refuse to abandon a good pun. (I regret appearing to have more compassion for puns than for horses.)

As part of the same vote X3J13 also changed `defstruct` to allow BOA constructors to have parameters (including supplied-p parameters) that do not correspond to any slot. Such parameters may be used in subsequent initialization forms in the parameter list. Consider this example:

    (defstruct (ice-cream-factory
                 (:constructor fabricate-factory
                   (&key (capacity 5)
                          location
                          (local-flavors
                            (case location
                              ((hawaii) '(pineapple macadamia guava))
                              ((massachusetts) '(lobster baked-bean))
                              ((california) '(ginger lotus avocado
                                              bean-sprout garlic))
                              ((texas) '(jalapeno barbecue))))
                          (flavors (subseq (append local-flavors
                                                   '(vanilla
                                                     chocolate
                                                     strawberry
                                                     pistachio
                                                     maple-walnut
                                                     peppermint))
                                           0 capacity)))))
      (capacity 3)
      (flavors '(vanilla chocolate strawberry mango)))

The structure type `ice-cream-factory` has two constructors. The standard constructor, `make-ice-cream-factory`, takes two keyword arguments named `:capacity` and `:flavors`. For this constructor, the default for the `capacity` slot is `3` and the default list of `flavors` is America's favorite threesome and a dark horse (not a dead one). The BOA constructor `fabricate-factory` accepts four different keyword arguments. The `:capacity` argument defaults to `5`, and the `:flavors` argument defaults in a complicated manner based on the other three. The `:local-flavors` argument may be specified directly, or may be allowed to default based on the `:location` of the factory. Here are examples of various factories:

    (setq houston (fabricate-factory :capacity 4 :location 'texas))
    (setq cambridge (fabricate-factory :location 'massachusetts))
    (setq seattle (fabricate-factory :local-flavors '(salmon)))
    (setq wheaton (fabricate-factory :capacity 4 :location 'illinois))
    (setq pittsburgh (fabricate-factory :capacity 4))
    (setq cleveland (make-factory :capacity 4))

    (ice-cream-factory-flavors houston)
     => (jalapeno barbecue vanilla chocolate)

    (ice-cream-factory-flavors cambridge)
     => (lobster baked-bean vanilla chocolate strawberry)

    (ice-cream-factory-flavors seattle)
     => (salmon vanilla chocolate strawberry pistachio)

    (ice-cream-factory-flavors wheaton)
     => (vanilla chocolate strawberry pistachio)

    (ice-cream-factory-flavors pittsburgh)
     => (vanilla chocolate strawberry pistachio)

    (ice-cream-factory-flavors cleveland)
     => (vanilla chocolate strawberry mango)

Next: Structures of Explicitly Up: Structures Previous: Defstruct Options