Island Life

< らむ太と小学校 | ピアノレッスン58回目 >

2012/09/01

再定義のセマンティクス

時の羅針盤@blog: exportされた変数

これコンパイルエラーにした方がいろいろ嬉しいのだが、それをやると非常に不便になる。以下が不便になる例。

(library (test)
    (export variable)
    (import (rnrs))
  (define variable 1)
  )
(library (test2)
    (export variable)
    (import (rnrs) (test))
  (define variable 'hoge) ; ここ
  (define (aa) (set! variable 2))
  )

R6RSやR7RS的にはコメントで示している部分はエラーにならないといけないんだけど、それやるとSRFIとかでガチンコでぶつかっている手続きがエラーになる。(removeとか、振る舞いまで違うから再定義せざるを得ない)。

まあR6RS的には、test2で (import (except (test) variable)) と書きなさいってことなんだけど、それはなぜか、というと、「importは何をしているのか」 という話になる。

R6RSのimportでは「識別子」というモノがあって、importはそれを 現在の名前空間に持ってくる。test2でtestをそのままimportしたら、 test2内でvariableという名前が意味するモノは、 testが意味するモノとまったく同じモノだ。

そして、Scheme世界全体として見た時に、 (単一フェーズで)おなじ「モノ」が二つの意味を持ってはいけない。

上の例で言えば、 (library (test3) (import (rnrs) (test)) variable) とした時に、 これはいくつになるべきか、という話になる。 このvariableが指す「モノ」はtestで定義されたvariableの指すモノと同じで、 それはtest2にもimportされてるからtest2でのvariableが指すモノとも同じでなければならない。 つまり、test3がtest2を参照してなかろうが、test2が実行された時点で 前文の「モノ」は全て同時に再定義されることになる。

Common Lispではシンボルが上述の「モノ」の役割を担っていて、 しかも再定義が許されているので、test2を実行(ロード)した段階でvariableの意味が変わる。 testしかimportしていないtest3にとっては、 test2のロード前と後でvariableの値が変わるように見える。

一方、Gaucheでは、importは可視性をコントロールする。importしても、 現在の名前空間に何かが「持ち込まれる」ということはない。 単に、「名前を解決する時に探す場所」が付加されるだけだ。 このセマンティクスでは、test2の中でvariableを定義すると、 それはモジュールtest2内に束縛を追加し、test1内の束縛をシャドウする。 (つまり、独立した定義の追加であって再定義ではない)。 この場合、test3から見えるvariableは依然としてtest1のものなので、 その値は変わらない。

実用上、これは大抵の場面で期待した動作をしてくれる。 例えばライブラリが刻々と拡張されてゆく場合、次のような状況が起き得る。

  • mylibraryがyourlibraryをimport
  • mylibraryでfooを定義
  • yourlibaryの新バージョンがfooを定義してexport

R6RSセマンティクスでは、yourlibraryの新バージョンを使う時に mylibraryのimport節を (import (except (yourlibrary) foo)) に 書き直さなければならない。

R6RS的には、新たな識別子がexportされるっていうのはAPIの変更なんだから それを使ってるモジュールに影響が出るのは当然って立場で、 それはそれで筋が通ってはいる。R6RSの世界観っていうのは「一貫した静的な世界が まずあって、変更はイレギュラーな要因」なんだよな。

Gaucheは「世界とは刻々変わってゆくもので、 一貫性はもともと部分的にしか保証できないが、 「ここまではだいたい保証できる」って範囲をできるだけ明らかにしとこう」って立場。

(ただまあ、R6RSでも「importしてる識別子を再定義したら自動的にexceptされたことにする」ってのは不可能ではない。R6RS制定時に何かそういう議論があったような気もする。 libraryフォームはマクロ展開前に解釈されなくちゃならなくて、defineされてるかどうかは マクロ展開後にしかわからないから、ちゃんとした意味を決めるのはひどく面倒になるが、 マクロ展開後にimportが差し替えられるとみなす、とか妥協すれば実装は可能だ。)

Tags: Programming, Gauche, Scheme, CommonLisp

Post a comment

Name: