Let's start Scheme

2014-02-03

Schemeのマクロにおける変数参照についてのメモ

自分の考えをまとめるためのメモ。主にSagittariusでの実装の話。

Schemeは変数のシャドウイングがある。そのためコンパイラが適切に変数を参照するためにはそれがどこで束縛されたかを知っている必要がある。これだけなら環境はスタックのように束縛された変数を持っておき、上から順に探索すればいいだけなのだが、問題になるのはhygenic macroで束縛された変数(便宜上テンプレート変数と呼ぶ)である。

例えば以下のケース
(define-syntax test
  (syntax-rules ()
    ((_ a b)
     (let ((a a))
       (+ a b)))))
(let ((a 1) (b 2)) (test b a))
というのは、3を返す必要がある。(今一いい例ではないかもしれないが)上記の例ではaがテンプレート変数ということになる。Sagittariusではテンプレート変数は基本的にリネームされ、実際に渡される式とは別の扱いになっている。リネームの再に現在のマクロ環境が用いられてリネームされるので生成される識別子aはこの場合は空の環境を持ったものになる。(実際にはマクロであることを識別するためのマークと定義されたライブラリの情報が入る)

この辺りまでならまだ混乱は少ないのだがR6RSにはdatum->syntaxがある。これが頭の痛い問題で、syntax-rulesでは不可能な「識別子が定義される環境を任意の場所に指定する」ことができる。例えば、CLで有名なaifは以下のようになる。
(define-syntax aif
  (lambda (x)
    (syntax-case x ()
      ((aif expr then else)
       (with-syntax ((it (datum->syntax #'aif 'it)))
         #'(let ((it expr))
             (if it then else)))))))
(aif (car '(a)) it #f) ;; => a
上記の例ではitはマクロaifが定義された環境(つまり空)を持つ識別子となる。そうすることでaif式内で参照されるitはあたかもグローバルに束縛された変数を参照するような挙動をする。しかし、現状の実装では以下のような場合に上手く動かない。
(define-syntax wrap
  (syntax-rules ()
    ((_)
     (aif 'ok it #f))))
(wrap) ;;=> should return ok but raises an error
一段マクロをかますことで、itがテンプレート変数に変更され環境が変わるからである。本来であればitは大域で定義された変数と同様な動きをするべきだが、そうなっていない。(wrapが局所で定義されたらどうするんだという話もあるがとりあえず放置・・・)

細かいケースを上げたらきりがないのだが、本題としてはどの場合にどの識別子を同一のものとみなすかということである。例えば同じ環境を保持しているのか、コンパイラ環境に格納された変数が、ターゲットの変数と環境を共有していればいいのか、とかそんな感じである。現状の実装ではマクロの定義時と展開時にリネームが走るのだが、その部分もおそらく見直す必要がある。余計な情報を付加しているか、または逆に情報の欠落が起きている可能性があり変数参照時に正しく参照できていないからである。

マクロのバグを踏むたびに既存の展開器を使っておけばよかったなぁと思ったりもするが、それだと面白くないというのもあったりで複雑な気分である。

No comments:

Post a Comment