Clementson's Blog

Bits and pieces (mostly Lisp-related) that I collect from the ether.

August 2004
Sun Mon Tue Wed Thu Fri Sat
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Jul  Sep

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.

Because a constructor of this type operates 'By Order of Arguments,' it is sometimes known as a 'boa constructor.'"
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:
;;; 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)
(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))))
He also pointed out that
"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*))
;; 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*))
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.

emacs Copyright © 2004 by Bill Clementson