Gauche:autoload:define-methodとの干渉

Gauche:autoload:define-methodとの干渉

Shiro(2007/08/10 16:13:42 PDT): define-methodによる暗黙のgeneric定義と、autoloadとの厄介な相互作用について。結構前から問題になっていたんだけれど、 綺麗な解決が思いつかないので保留していた。

問題

define-methodは、定義されるメソッドと同名のgeneric functionがその時点で 存在するかどうかを調べ、無ければgeneric functionを定義する (暗黙のgeneric定義)。

0.8.10の時点では、この「同名のgeneric functionを探す」時にautoloadの解決を 行わない。つまり、次のようなコードがあった場合:

(autoload "foo" foo)
(define-method foo ((x <string>)) (print "string:" x))

"foo.scm" のロードは行われず、新たなgeneric function 'foo' が define-method により定義される。"foo.scm"の中で'foo'に他のメソッドが定義されていたとしても、 そちらとは別のgeneric functionになってしまうため、多相的にfooを呼ぶことが 出来なくなる。 (もしなんらかの事情でdefine-method fooの前にfooが評価されれば、autoload が効いて、このメソッドが"foo.scm"中で定義されるメソッドと同じgeneric functionに 統合されることになる)。

define-methodが同名のgeneric functionを探す時にautoloadの解決を 行えば良いと思うかもしれないが、そうすると今度は"foo.scm"のロード時に 問題が起きる場合がある。もし"foo.scm"が次のような内容であった場合:

(define-method foo ((x <list>)) (print "list:" x))

autoloadの循環は束縛解決ルーチン(Scm_GlobalVariableRef)内で検出・処理されちゃうんで、 define-methodの場合だけ特別扱いというのがやりにくい。

また、fooの束縛の状態遷移 (autoloadオブジェクト→本来のgeneric function) は アトミックに行われる必要がある。例えば「まずautoloadを解決せずにfooの 束縛を探し、結果がautoloadオブジェクトだったらfooに一時的に他の値を 束縛してからautoloadを解決する」というような手段は取れない。 なぜなら「fooに一時的に他の値を束縛」している瞬間に他のスレッドがfooを 参照するかもしれないので。 autoloadの解決はちゃんとシリアライズされているので、 複数のスレッドがfooがautoloadであることを 見てautoload解決ルーチンに入ることは問題ない。

解決策あれこれ

とりあえず、autoload解決が再帰した時に単にエラーにするのではなく SCM_UNBOUNDを返すような特別なフラグをAPIに追加することでこの問題 そのものは解決することを確認。%ensure-generic-functionのような 「束縛の存在確認」をするような場合はそのフラグを立ててやれば、 「解決中のautoload変数の参照」は「未束縛」扱いになる(実際、それは まだ定義されていないわけで)。

しかし、このためにScm_GlobalVariableRefとScm_LoadAutoloadの 2レイヤに渡ってフラグを追加しなければならないのがどうも美しくない。 上位層の都合で下位層の振舞いをカスタマイズしなくちゃならないってのは 抽象化が上手くない可能性がある。

「解決中のautoload変数の参照」を常に「未束縛」扱いにするのはどうだろうか。 autoloadの解決は変数の値を参照する際にトリガされ、変数の値を受け取る方は SCM_UNBOUNDが帰って来れば未束縛変数としてエラーをシグナルするのだから、 うっかりautoloadが循環してしまったようなケースは依然としてエラーを通知できる。

ただ、エラー内容が「autoloadが循環してるよ」ではなく単に「変数が未定義だよ」 になるから、原因の追求が面倒になる可能性はある。

現実に、うっかりautoloadが循環してしまうようなケースはそれほどあるだろうか。 autoloadされるファイルを分割して多段autoloadにした場合なんかにうっかり やりそうな気はするけど、その場合は原因の特定は楽なように思える。 とりあえずフラグの引数だけAPIに追加しといて、「循環は未束縛扱い」でリリースして 様子をみてみようか。

More ...