2016/06/17
スロットの初期化をチェックする
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
Post a comment