Common Lisp の構造体はCの構造体と同じで、中身はただの配列、かつfancyなイニシャライザのようなものはない。スロット(Cにおけるメンバ)の初期値を指定できるだけである。これでは不便。
たとえば、下のようなあまり意味のないhashtableのwrapperを作ってみるとする。
(defstruct hash
(hash (make-hash-table) :type hash-table))
これでは、make-hash-tableに:test
や:synchronized
などのキーワード引数を与えることが出来ない。
(defvar *h* (make-hash))
(setf (hash-hash *h*) (make-hash-table :test 'equal))
これでは不便だなあ。非効率だなあ。
ところで、defstruct
には :constructor
オプションがある。これをいじればナントカなる気がする。
(defstruct (hash (:constructor make-hash (&key hash)))
(hash (make-hash-table) :type hash-table))
(defvar *h* (make-hash :hash (make-hash-table :test 'equal)))
しかしこれでも、 make-hash-table
を明示的に書かないといけないのはイケてない気がする。
とおもいきや、実はこんなことが出来る。
(defstruct (hash (:constructor make-hash (&key (test 'eql)
&aux (hash (make-hash-table :test test)))))
hash)
LIB> (make-hash :test 'equal)
#S(HASH :HASH #<HASH-TABLE :TEST EQUAL :COUNT 0 {10115325C3}>)
LIB> (make-hash :test 'eql)
#S(HASH :HASH #<HASH-TABLE :TEST EQL :COUNT 0 {1011534233}>)
LIB>
:constructor
オプションを使えばコンストラクタの関数名と引数シグネチャを変更できるのだが、引数の名前がスロットの名前に一致していれば、その変数の値がスロットに代入される。また、引数は左から順に評価され、また先に評価された引数の値を見ることが出来る。そこで、関数に好きなだけ変数もどきを追加できる &aux
を使えば、イニシャライザやメソッドなどがなくても、任意の処理を行うことが出来る。
この用法は boa lambda list で明示的に許されている。
さて、それでは同じ方法で slot-option
のほうの値を操作することはできるだろうか? CLHSによると slot-initform
は defstruct
の定義された lexical environment
で評価されるのであって、コンストラクタの引数はこれに影響を与えることができない。すなわち、以下は許されない。
(defstruct (hash (:constructor make-hash (&key (test 'eql))))
(hash (make-hash-table :test test)))
のはずなんだけど、SBCLではこれができてしまう。
LIB> (make-hash :test 'eql)
#S(HASH :HASH #<HASH-TABLE :TEST EQL :COUNT 0 {1005A13EF3}>)
LIB> (make-hash :test 'equal)
#S(HASH :HASH #<HASH-TABLE :TEST EQUAL :COUNT 0 {1005A17D93}>)
conformしてないといえばしてない。たとえば、以下の動作は非準拠だと思われる。
(let ((test 'equal))
(defstruct (hash (:constructor make-hash (&key (test 'eql))))
(hash (make-hash-table :test test))))
LIB> (make-hash)
#S(HASH :HASH #<HASH-TABLE :TEST EQL :COUNT 0 {1005A196C3}>)
正しくは lexical environment の値を使うべきなので #S(HASH :HASH #<HASH-TABLE :TEST EQUAL :COUNT 0>)
が返ってくるはずである。