er-macro-transformerのrenameは衝突を回避するだけじゃない
2023-11-08
TechSchemeの話。
Gaucheでsyntax-rulesとer-macro-transformerを試してたんだけど、少し挙動が違う点に気づいた。(Gaucheを使ったのは、単に両方を実装している処理系として最初に思いついたため)
まず、以下のように定数thePiを提供するpi.scmがあるとする。
(define-module pi
(export thePi)
(begin
(define thePi 3.14)))
それを使うutils.scmがあるとする。 utilsは、マクロloggを提供する。そのマクロの実装に先程のthePiを使うとどうなるか?
(define-module utils
(use pi)
(export logg)
(begin
(define-syntax logg
(syntax-rules ()
((_ str) (display thePi))))
結果だが、以下のようにthePiの値が表示された。main.scmではモジュールpiをuseしていないわけだが、これが許される方が、マクロの利用者としてはこのほうが使い勝手がよいといえる。だって「マクロを使うときにはその実装に使われたすべてのモジュールを再度useしなければならない」だと面倒でしょ。
> gosh -I. main.scm
3.14
一方で、er-macro-transformerの場合
utils.scmを以下のように書き換えてみる。
(define-module utils
(use pi)
(export logg)
(begin
(define-syntax logg
(er-macro-transformer
(lambda (form rename compare)
`(display thePi))))))
今度は、main.scmでthePiという変数が見つからないというエラーになった。
>gosh -I. main.scm
*** ERROR: unbound variable: thePi
While loading "./main.scm" at line 8
Stack Trace:
_______________________________________
0 thePi
[unknown location]
これは、展開結果の(display thePi)
がmain.scm側で評価されるから。このエラーをなおすには、以下のようにrename
を使ってやる。
(er-macro-transformer
(lambda (form rename compare)
`(display ,(rename thePi)))))
ER macroのrenameは「呼び出し側の名前との衝突を避ける」という文脈で説明されることが多いが(Gaucheでの説明)もそうなっている)、呼び出し側でimportされていない名前を使いたい場合にもrenameが必要、という話でした。
(追記:syntax-rulesの場合、展開結果が自動的にrenameされる、といえる。)