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