問題になったのは、マクロの展開とコンパイラの環境が密な関係にあることである。マクロ展開時にはコンパイラが集めた環境フレームを利用しているのだが、局所マクロを先に展開してしまうとそれを当てにした変数参照が動かなくなる。端的なコードしては以下のものがだめになる。
(define (bar) (let-syntax ((foo (syntax-rules () ((_ b) (when (< b 10) (bar)))))) (define (buz b) (foo b)) (buz 10)))これが展開後には以下のようになる。
(define (bar) (define (buz b) (when (< #<id b> 10) (bar))) (buz 10))問題になるのは識別子#<id b>で現在のマクロ展開器ならば識別子が持つ環境フレームに変数bが入って変数参照手続きがたどれるようになっている。しかし、ナイーブな実装で先にマクロだけを展開してしまうと生成された識別子は局所変数の参照を持たないフレームを持つことになり変数参照がうまくいかない。
これを解決するとすれば以下の2通りだろう。
- 識別子が持つフレームが参照する変数を含んでいない場合には共有している環境以前のみを探してみつける
- マクロ展開器が変数束縛を検知する
2は環境フレームの同値性に頼っているコードが山ほどあるので事実上不可能。やれなくはないが、複雑怪奇に(ry
となるとこの方向性で解決するには、全てのマクロをあらかじめ展開してしまうというR6RSが要求している方針を採らざるを得なくなる。マクロの展開とコンパイルを同時にやるというのは不可能ではない(はずな)のだが、現状のコンパイラは中間表現にGauche由来のIFormを使っているためS式との混在がきつい。
となるともう一つの方法である現状のコードを拡張する方向だが、これはこれでまたバグの温床になりそうな雰囲気が既に漂っているのでうれしくない。ちょっと方針に以下の項目も入れる方向で検討することにする。
- IFormを捨てる
- 大幅なコンパイラの変更が必要
- マクロ展開フェーズを設ける
- 重複コードをどうするか?
No comments:
Post a Comment