Traditional Macroの実装に悩む
Traditional Macro の実装をどうするか考えるために Gauche の define-macro の動きを調べていた。
Gauche のマニュアルによれば
define-macro name procedure
When a form (name arg ...) is seen by the compiler, it calls procedure with arg ....
When procedure returns, the compiler inserts the returned form in place of the original form, and compile it again.
おおざっぱに言うと Macro はその場で2回評価される感じっぽい。
"it calls procedure with arg" の部分をどうも誤解していたようで
(define-macro macro-test (lambda (a) `(display (quote ,a)))) (macro-test (+ 1 2)) => (+ 1 2)
となることから、arg はS式のままで(quoteされて) procedure に渡るようだ。
なるほどこの仕組みにしておけば、マクロを呼び出す側で quote せず form そのものを渡せるのか。
仕組みと意図は理解できたのだけど、自分の処理系で実現するにはちょっと面倒そうだ。
僕の処理系では (hoge (+ 1 2)) を構文解析時にC++のオブジェクト(中間形式)に変換してしまう。
疑似コードで書くと
new Application(new Varible("hoge"), [new Application(new Varible("+")), [new Number(1), new Number(2)]])
という感じ。
今までは手続き呼び出しだけを考慮すれば良かったのだけど
例えば以下のようなコードがあったとして
(hoge (+ 1 2))
実行時するまでは hoge が手続きなのかマクロなのか分からないので引数を
new Application(new Varible("+")), [new Number(1), new Number(2)]
と変換するのか S式のまま保持しておくべきなのか決められない。
というか define-syntax でも同じ問題が起こることに今気づいた。
書きながら考えが整理されてきたのだけど、マクロのように
- 実行時まで手続き呼出しと区別がつかない
- 引数は S式 のままでうけとりたい
という仕組みがある以上、引数は全てS式で保持しておいたほうが良さそうだ。
そうすると引数はS式で、他のものは処理系の中間形式に変換されるというなんだか気持ち悪い感じに。
構文解析時に中間形式のオブジェクトに変換するのやめて、eval時に直接S式を全部解釈した方が良いのではないかと思えてきた。
とりあえず引数は S式で保持というのをやってみるかな。