スロットの初期化をチェックする

Gaucheのオブジェクトシステムでは、オブジェクトの作成時にスロットを初期化しないで おくことができる。初期化されないスロットはunboundという状態になる。次の例では、 スロット a には初期値 ( :init-value もしくは :init-form )が 設定されていないので、 make に :a で初期値を渡さない限り、 作られるオブジェクトの a スロットはunboundになる。

gosh> (define-class A () ((a :init-keyword :a) (b :init-keyword :b :init-value 1))) A gosh> (make A) #<A 0xb280e0> gosh> ,d #<A 0xb280e0> is an instance of class A slots: a : #<unbound> b : 1

describeでは #<unbound> と表示されているが、 #<unbound> という 値が入っているわけではない。unboundはあくまでスロットの状態であって、 値を取り出そうとするとエラーになる。

gosh> (~ (make A)'a) *** ERROR: slot a of object of class #<class A> is unbound Stack Trace:

この仕様はCLOSに準じたものだ。

unbound slotは、インスタンス初期化中に、複数のメソッド間で情報をやりとりするのに使える。 スロットの初期化( make に渡された初期化キーワードの処理と、 :init-value や :init-form の処理) はベースクラスの initialize メソッドで処理されるので、自分の initialize メソッド中で スーパークラスのメソッドを呼んだ後にスロットがboundかどうかチェックすることで、 独自の初期化処理を書ける。特に、既に初期化された他のスロットに依存する計算を初期化時に行える。 次の例では、上で定義したクラス A について、初期化キーワード :a が make に渡されなかった場合に、 b スロットの値に基づいた初期化を行う。

gosh> (define-method initialize ((obj A) initargs) (next-method) (unless (slot-bound? obj 'a) (set! (~ obj 'a) (+ (~ obj 'b) 1)))) #<generic initialize (18)> gosh> (make A :b 10) #<A 0xb655c0> gosh> ,d #<A 0xb655c0> is an instance of class A slots: a : 11 b : 10 gosh> (make A :a 1 :b 10) #<A 0xb72c60> gosh> ,d #<A 0xb72c60> is an instance of class A slots: a : 1 b : 10

現実のプログラムでのunboundスロットの使いどころってほぼこれくらいで、 unbound slotを抱えたままのインスタンスをずっと持ち歩いて嬉しいことというのは 多分ほとんどない。unboundスロットへのアクセスはメソッド slot-unbound で インターセプトできるので、 それを利用したトリックはあり得るけど、特殊な用途と言えるだろう。 むしろ、初期化キーワードを間違えていたりして 初期化したつもりがされてない、なんてバグの方に足を取られることがある。 (関連して、 make に渡す初期化キーワードに無効なものがあったらエラーに できないか、という話もあるのだけれど、初期化キーワードの解釈は allocate と initialize メソッドの定義次第でどうにでもできるので 機械的にはじくのは難しい)。

それなら、初期化が済んだ時点でスロットをチェックしてunboundなものをはじいて しまったらどうか。MOPを使って make にメソッドをつければ 全ての初期化が終わった後のタイミングで検査ができる。

(define-class <ensure-slot-initialization-meta> (<class>) ()) (define-method make ((class <ensure-slot-initialization-meta>) . args) (rlet1 instance (next-method) (dolist [s (class-slots class)] (unless (slot-bound? instance (slot-definition-name s)) (errorf "Slot ~s of object ~s is not initialized" (slot-definition-name s) instance)))))

こんな感じ。

gosh> (define-class B () ((a :init-keyword :a) (b :init-keyword :b :init-value 1)) :metaclass <ensure-slot-initialization-meta>) B gosh> (make B :a 1 :b 10) #<B 0xbb4ef0> gosh> ,d #<B 0xbb4ef0> is an instance of class B slots: a : 1 b : 10 gosh> (make B) *** ERROR: Slot a of object #<B 0xbbcc20> is not initialized Stack Trace:

これなら初期化キーワードを間違えたケースも(それによって意図したスロットが unboundになりさえすれば)捕まえられる。

さて、 <ensure-slot-initialization-meta> は MOPライブラリにしようと思って書いたのだけれど、 かなり本質的な機能のような気がするし、むしろ標準の <class> に :disallow-unbound-slot みたいなオプションをつけてしまう方が 良いような気がしてきた。どうしようかなあ。

Tags: Programming, Gauche