Initializing structures in CL using BOA Constructors
Sunday, August 22, 2004
In discussing initialization of structures, the Common Lisp Hyperspec states:
" If :constructor 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 and possibly by keywords. 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.On c.l.l. John Hinsdale asked for some advice on how best to initialize an array of structures with some static data. He provided some example code showing what he was trying to achieve:
Because a constructor of this type operates 'By Order of Arguments,' it is sometimes known as a 'boa constructor.'"
;;; EXAMPLE ....
(defstruct stooge first-name last-name quotation)
(defvar *initialdata* ; First Last Quote ; -------- -------- ----------------------------- '(("Moe" "Howard" "Spread oooouuuut!") ("Larry" "Fine" "Why you ... I oughtta") ("Curly" "Howard" "Moe! Larry! The Cheese! Woohoowoo!")))
(setf stooges (make-array 3 :adjustable t :fill-pointer 0))
; Init to the first three (mapcar #'(lambda (l) (vector-push (make-stooge ; *** THIS IS UGLY? *** :first-name (elt l 0) :last-name (elt l 1) :quotation (elt l 2)) stooges)) *initialdata*)
(print stooges)
; ... later on in the program (vector-push-extend (make-stooge :first-name "Shemp" :last-name "Howard" :quotation "I'm not like Curly") stooges)
(print stooges)
Frank Buss showed how to do this using the reader macro for structures:
(defstruct test f1 f2 f3) (setf records '(#s(test :f1 1 :f2 2 :f3 3) #s(test :f1 4 :f2 5 :f3 6))) (setf records (append records '(#s(test :f1 7 :f2 8 :f3 9)))) (test-f3 (car records))
Christophe Rhodes illustrated an alternative technique using BOA Constructors:
(defstruct (stooge (:constructor make-stooge) (:constructor boa-make-stooge (first-name last-name quotation))) first-name last-name quotation)He also pointed out that
(defvar stooges) (setf stooges (make-array 3 :adjustable t :fill-pointer 3 :initial-contents (loop for i in *initialdata* collect (apply #'boa-make-stooge i))))
"You could alternatively avoid the need for the boa constructor if you defined this initialization list a bit more like a table: '((:first-name "Moe" :last-name "Howard" :quotation "Spread oooouuuut!") ...)"He points out though that "... with this structure, adding/changing/removing an element will involve modifying _all_ lines of the file, making the delta noisier".
Marco Baringer provided 2 alternatives (one using destructuring-bind and one using BOA Constructors):
;; option 1:
(setf stooges (mapcar (lambda (spec) (destructuring-bind (first-name last-name quote) (make-stooge :first-name first-name :last-name last-name :quotation quote))) *initialdata*))
;; option 2 (if you want to maintain the visual formatting of the data):
(defstruct (stooge (:constructor make-stooge) (:constructor make-stooge* first-name last-name quotation)) first-name last-name quotation)
(defvar *stooges* (make-array 3 :adjustable t :fill-pointer 0 :initial-contents (list ;; first last quotation (make-stooge* "Moe" "Howard" "Spread oooouuuut!") (make-stooge* "Larry" "Fine" "Why you ... I oughtta") (make-stooge* "Curly" "Howard" "Moe! Larry! The Cheese! Woohoowoo!"))))
Christophe Turle gave an example using loop:
(loop for (first-name last-name quotation) in *initialdata* do (vector-push (make-stooge :first-name first-name :last-name last-name :quotation quotation ) stooges ))
While Pascal Bourguignon illustrated how this example could be further refined:
(defparameter stooges (map 'vector (lambda (rec) (make-stooge :first-name (first rec) :last-name (second rec) :quotation (third rec))) *initialdata*))Which all goes to show you that there are many ways to skin a cat in Common Lisp! In the end, John Hinsdale wound up using a variation on the BOA Constructor technique suggested by Christophe Rhodes.
;; It could be even simpler with:
(defstruct (stooge (:type list)) first-name last-name quotation)
(defparameter stooges (make-array (list (length *initialdata*)) :initial-contents *initialdata*))
;; Note that it depends on the number of fields. For a large number, I'd do:
(defparameter stooges (map 'vector (lambda (rec) (apply (function make-stooge) (mapcan (lambda (field value) (list field value)) '(:first-name :last-name :quotation ...) rec))) *initialdata*))

