一方、他の Lisp の仲間である Common Lisp や Emacs Lisp では、
dynamic scope を採用しており、関数の呼び出された側にも呼び出した側の束縛関係が
適用されます。Emacs Lisp で試してみると良いでしょう。
letrec の用いられる例としてよくいわれるのは、(相互)再帰をもちいた program です。
lambda 式 などをもちいて書かれます。
(define (foo x)
(+ (debug-print "outer-before:" var0)
(let ((var0 (* x x))
(var1 (* 2 (debug-print "binding:" var0))))
(+ (debug-print "body:" var0)
(debug-print "callee:" (foo-callee))
(let ((var2 3)) (+ (debug-print "in:no-conflict:" var0) var2))
(let ((var0 3)) (debug-print "in:conflict:" var0))
var1))
(debug-print "outer-after:" var0)))
> (foo 2)
outer-before:10 ; a: let の外では無効
binding:10 ; b: 他のlet節の中でも無効
body:4 ; c: body 部では当然有効
callee:10 ; d: 呼び出した関数の中では無効
in:no-conflict:4 ; e: 入れ子の let の中では衝突しない限り有効
in:conflict:3 ; f: 内側の let の束縛に負けて無効
outer-after:10 ; g: let の外では、後でも無効
64 ; (foo 2) の値の出力
(let* ((var-1 expr-1)
....
(var-k expr-k)
....
(var-n expr-n))
body)
但し、各 expr-i の評価時に別の var-j が定まった値を持っている保証は
ありません。
つまり、
(letrec ((x (+ y 1))
(y (+ x 1)))
(+ x y))
などという program が動くわけではありません。
(define (foo x)
(letrec ((myodd?
(lambda (n) (if (= n 0) #f (myeven? (- n 1)))))
(myeven?
(lambda (n) (if (= n 0) #t (myodd? (- n 1))))))
(myodd? x)))
実は、 define の提供する世界、
つまり大域的に変数名を宣言し、再帰呼び出しなどもできる世界は、
letrec の中に実現することが出来るわけです。
99.10.6/ Tomio KAMADA: kamada@cs.kobe-u.ac.jp