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を確認すると,こういうdefineletrecと等しいと書いてある.つまり,

(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においてdefineletrecで挙動が異なるのは直感的ではないが,そもそも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しちゃってるあたりが邪悪ですが,とりあえずプログラムの意味は破綻しません.

*1:バージョンは0.8.11 [utf-8,pthereads]