LoginSignup
1
2

More than 5 years have passed since last update.

構造体のスロットをオプション的に自由に初期化したい

Last updated at Posted at 2018-01-04

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-initformdefstruct の定義された 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>) が返ってくるはずである。

1
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2