Common Lisp the Language, 2nd Edition
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)