現状の問題としては、同一の環境でリネームされた識別子(本来はシンボル)が同名にならないというものだ。これは本質的に2つの問題を含んでいることを示唆している。
- 識別子を用いてシンボルの同一性を取ろうとしていること
- α変換が不完全である
- 識別子が本来の意味を越えて使用されているため、複雑なものが複雑怪奇にグレードアップしている
- 識別子=構文オブジェクト=パターン変数=リネームされたシンボルになっている
- 使用されている環境に応じて適切にシンボルのリネームを行う
- コンパイル時の環境
- マクロ束縛時の環境
- マクロ展開時の環境
(import (rnrs))
(let ((bv #t)) ;; ※1
(define-syntax foo
(lambda (x)
(syntax-case x ()
((_)
(with-syntax ((set (let ((type #'bytevector-u8-set!))
#`(#,type bv 0 1))))
#'(let ((bv (make-bytevector 1)))
set
bv))))))
(let ((bv #f))
(display (foo))))
まぁ、前回とほぼ同じコードである。違いは、マクロ束縛時環境が何かを捉えている点である。ここで問題になるのは、全てのbvが同一のシンボルになる必要がある点である。そうすると、コンパイル時環境を使うのはまずいことになる。letで束縛されたtypeが入ってくるからだ。そうすると、二つ目のbvと環境が異なるため、生成されるシンボルが異なる。この状態では、マクロ束縛時と展開時の環境が多少違う。このパターンを考えるとマクロ束縛時の環境を使ってリネームするのが正しい気がする。ただし、リネームされたシンボルは※1と同名になる必要がある。これは、
with-syntax>で生成されたものがそれを参照している必要があるからだ。また、最終的に返される構文で束縛されるbvも同様である。それによってシャドウイングされて結果正しい値を返す。もし、このスクリプトでマクロ展開時の環境を使うと、(まぁ、結果的には問題ないのだが)、二つ目のbvにリネームされることになるのでうれしくない。
なんとなくどうすればいいのか見えてきた。結果として以下のように修正すれば、いいのではないだろうか?
- コンパイラ側のリネームを(uninterned)シンボルを使うように修正
- マクロ展開器ではマクロ束縛時の環境を元にシンボルを(uninterned)シンボルにリネーム