たしかにコレ納得が難しい気がする

納得する必要が有るのは、多分"R6RS syntax-rulesの展開は1passでは無理"というポイントで、2passでやることを覚悟しながら考えると理解しやすい気がしている。

(実際にはR5RSやR7RSではsyntaxの相互参照 が禁止されている をサポートする必要が無いので、やる気になれば1passでできる気がする)

まずはdefine-syntaxではなくdefineだけを使った簡単な例:

( define ( bar ) ( display theNumber )) ( define theNumber 42 ) ( bar )

この例で、barの定義時にはtheNumberはまだプログラム中に出現していない点に注意する。それでもこのプログラムを正当なプログラムと判断するためには、(display theNumber)を処理する段階でdisplayとtheNumberが構文でなく変数であることを何らかの方法で知っている必要がある。つまり、Schemeコードをコンパイルするためには最初にdefine(や、define-syntax)を収集して、あるシンボルがbindされているのかを見定めなければならない。

gistの例に戻ると:

"1" が出力されるケース(単純なケース)

( let () ( define-syntax foo ( syntax-rules ( LIT ) (( _ LIT ) "1

" ) (( _ otherwise ) "otherwise

" ))) ( display ( foo LIT )))

このケースは単純。LITはbindされていないので構文foo内のリテラルとしてマッチする。

"1" が出力されるケース(bindされたケース)

( let () ( define-syntax foo ( syntax-rules ( LIT ) (( _ LIT ) "1

" ) (( _ otherwise ) "otherwise

" ))) ( define LIT 3 ) ( display ( foo LIT )))

このケースは最初のbarの例に近い。コンパイラは最初にプログラムをスキャンしてfooが構文、LITが変数であることを知ってから、syntax-rulesを処理する必要がある。

"otherwise"が出力されるケース

( let () ( define-syntax foo ( syntax-rules ( LIT ) (( _ LIT ) "1

" ) (( _ otherwise ) "otherwise

" ))) ( let (( LIT 3 )) ( display ( foo LIT ))))

この場合は、letでbindされるLITと、syntax-rulesのリテラルリストにあるLITは別物ということになる。

環境の内容は特にdefine(-syntax)で定義されるものである必要はなく、let/letrecでも同様のことが言える。

( let (( LIT ( foo LIT ))) ( display LIT )) ( letrec (( LIT ( foo LIT ))) ( display LIT ))

letの場合は、(foo LIT)を処理する環境ではLITはbindされていないため、1が出力される。letrecの場合は、(foo LIT)を処理する環境にはLITがbindされているため、otherwiseが出力されることになる。(このとき、letrecケースもエラーや未定義にはならないことに注意する - 構文fooを処理する際にLITの内容そのものは使用しないため、bindされたか否かだけが影響している。)

つまり、syntax-rulesのリテラルリストは:

この性質で大きく問題になるのはR6RS/R7RSで補助構文をrenameした場合で、例えば、elseのようなidentifierをrenameした場合の挙動がかなりマチマチになっている。

( import ( except ( scheme base ) else ) ( scheme write ) ( rename ( scheme base ) ( else my-else ))) ( case #f ( my-else ( display "Oh!

" )))

のようにして、elseをrenameしたときの挙動をyunibaseの収録scheme処理系で調べてみると:

root@5895067554f4:/yunibuild# yunified/chez-scheme /import/check3.scm Oh! root@5895067554f4:/yunibuild# yunified/sagittarius /import/check3.scm Oh! root@5895067554f4:/yunibuild# yunified/nmosh /import/check3.scm Oh! root@5895067554f4:/yunibuild# yunified/chibi-scheme /import/check3.scm Oh!

のようにR6RSの殆んどの処理系とchibi-schemeは期待通りelseをmy-elseにrenameできているが、Gauche、Guile、Chickenはrenameできていない(実行するとエラーになる - 課題: エラーにならないような判定プログラムを記述できるか)。

elseをrenameできない処理系ではelseはbindされておらず、字面上の名前が一致しないとelseとして使用できないということになる。