symbol-bound? (Re: [Gauche-devel-jp] IPv6サポートへ向けて)

Shiro Kawai shiro****@lava*****
2003年 5月 3日 (土) 19:39:25 JST


実は、トップレベルの束縛というのは明確にセマンティクスが
与えられていない、Schemeのダークサイドなのです。
symbol-bound?のようにトップレベルの束縛情報を実行時に
扱う構造は鬼門でして、どうしても必要な時に使うハックと
思っておいてもらえると良いです。

敢えて説明すると、コンパイル時のモジュールと実行時の
モジュールが違う場合がある、というのがこの奇妙なふるまいの
原因です。

Gaucheはトップレベルフォーム毎にまずコンパイルして、
それを実行します。また、「カレントモジュール」は
コンパイル時のトップレベル変数の束縛の解決に影響します。

しかし、symbol-bound? は(第2引数が与えられない場合)
実行時のカレントモジュール中から束縛を探します。
symbol-bound? は通常の手続きなので、実行される時には
コンパイル時のカレントモジュールのことなど知らないのです。

                  * * *

例えば #<module user> にて、Gaucheがこいういう
フォームを見たとしましょう。

(define-module mod1 (define a 1) (symbol-bound? 'a))

Gaucheはこのフォーム全体をまずコンパイルします。define-module
は構文要素なのでコンパイラに認識され、ボディ部分である
  (define a 1) (symbol-bound? 'a)
の二つのフォームは #<module mod1> 内でコンパイルされます。

(define a 1) は、「#<module mod1> 内に a というトップレベル
変数を作って1に束縛する」というコードにコンパイルされます。

一方、(symbol-bound? 'a) は、トップレベル変数の symbol-bound?
が構文ではないため、普通に関数呼び出しのコードが生成されます。

さて、コンパイルが済んだところでGaucheはこのコードを
実行します。コードが実行されるのは最初と同じ、#<module user>内です。

(define a 1) はどこで実行されようが、#<module mod1>内に
aの束縛を作ります。
一方、(symbol-bound? 'a) では、symbol-bound? は
実行時のカレントモジュールである #<module user> から a の
束縛を探してしまいます。

(symbol-bound? 'a (current-module)) だとどうでしょう。
current-moduleは特殊形式であり、コンパイル時にその時点の
カレントモジュールである #<module mod1> へと展開されるので、
実行時にも symbol-bound? が #<module mod1> からaの束縛を
探して来られるというわけです。

  (select-module mod1)
  (define a 1)
  (symbol-bound? 'a)

このようにトップレベルに書いた場合、各フォームのコンパイル、
実行が順番に行われるため、 (symbol-bound? 'a) はコンパイルも
実行もともに #<module mod1> をカレントモジュールとして
行われます。したがって a の束縛を #<module mod1> から探します。

                  * * *

実行時のカレントモジュールをコンパイル時のカレントモジュールに
合わせていないのは、性能の問題です。
技術的には、(define-module mod1 ...) をコンパイルした時に、

  - カレントモジュールを mod1 に切替え
  - ... のコード
  - カレントモジュールを戻す

というコードに展開してやればいいのですが、 ...の中でエラーが
起きた場合に正しくカレントモジュールを戻すために、dynamic wind
のスタックを積む必要があります。すると、with-moduleのような
構文をインナーループで使っている場合等にかなり大きなペナルティと
なってしまいます。

symbol-bound? を構文にしてしまえばこのような矛盾は無くなるの
ですが、どうしても実行時に判断したい場合というのもあるので、
手続きにしています。

--shiro



Gauche-devel-jp メーリングリストの案内
アーカイブの一覧に戻る