Dr.SchemeのlocalをGaucheで実現する
大嘘だったので削除.ブラウザの履歴から復元できました.
Dr.Schemeにはlocalという構文がある.
(local ((define x 1)) (+ x 1))
などとして使って,xがローカル変数になって外部からは見えなくなる,というもの.これは当然,R5RSには無い構文で,Dr.Scheme以外には用意されていない.もろもろの事情があって,これをGaucheにポートしなくてはいけなくなった.
一番最初に考えたマクロは以前書いたこともあるのだが,こういうやつ.
(define-syntax local (syntax-rules () ((_ () e ...) (let () e ...)) ((_ (d ...) e ...) (let () d ... e ...))))
これだと,上に書いたようなプログラムはこういう風に変換される.
(let () (define x 1) x)
letの本体には,定義を書くことができるので,これで良いように思えた.
ところが,こういうプログラムの場合,上手く動かないことが判明した.
(local ((define xs (list 1 2 3)) (define ys (reverse xs))) ys)
'(3 2 1)になってほしいのだが,undefになってしまう.これはGauche*1のバグだろうか?
R5RSを確認すると,こういうdefineはletrecと等しいと書いてある.つまり,
(let () (define xs (list 1 2 3)) (define ys (reverse xs)) ys)
は
(let () (letrec ((xs (list 1 2 3)) (ys (reverse xs))) ys))
と等しいらしい.試してみると,Gaucheでは,letrecの場合とdefineの場合で実行結果が違った(letrecの場合は'(3 2 1)になる).これはバグではないか!?
と思っていたのだが,mixiのコメント等で教えていただいたところによると,R5RSが設けているところのletrecの制限に引っ掛るとのことだった.ysの定義中でxsを参照しているので,このletrecはエラーにならなくてはいけない.まとめると,Gaucheにおいてdefineとletrecで挙動が異なるのは直感的ではないが,そもそもletrecの処理が(特にinline展開を有効にした場合に)おかしいので,バグとまでは言い切れない,といった感じか.
ここまで調べた時点で,落ち着いて当初の目的を考えてみた.まず,PLTのlocalの意味を考えなくてはいけない.MLを調べていると,グローバルなdefineと同じ,という説明が見付かった.なるほど.これならDrSchemeの挙動は確かに正しい.っっっって,マクロにするのがめんどくせぇ.
というわけで,最後の武器であるところのevalを使って書いたのがこういうマクロ.
(define-macro (local defs . expr) (let* ((name-of-define (lambda (def) (let ((name (car (cdr def)))) (if (pair? name) (car name) name)))) (subst (map (lambda (def) (cons (name-of-define def) (gensym))) defs))) (letrec ((apply-subst (lambda (expr) (if (list? expr) (map apply-subst expr) (let ((p (assoc expr subst))) (if p (cdr p) expr)))))) (begin (for-each (lambda (e) (eval e (interaction-environment))) (map apply-subst defs)) (cons 'begin (map apply-subst expr))))))
束縛変数もそのままrenamingしちゃってるあたりが邪悪ですが,とりあえずプログラムの意味は破綻しません.