補足 ( let の話)


R5RS 情報(詳しくはこちら)

なぜに let ?
英語の論文とか読んだことのある人ならば当然の話かもしれませんが、 証明などでもでて来る「 y = f(x) とする/おく」などという言い回しは、 "Let y be f(x)" とか、"Let y = f(x)" などに対応します。

値を表示させて変数の有効範囲を調べてみましょう。
debug-print を挟んで、 変数の有効範囲を調べてみました。 こんな感じで調べればいいのです。

(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) の値の出力

static scope と dynamic scope
scheme のように関数の呼び出された側では呼び出した側の束縛関係(環境と呼ばれる)が 無視する場合、 static scope であるといいます。

一方、他の Lisp の仲間である Common Lisp や Emacs Lisp では、 dynamic scope を採用しており、関数の呼び出された側にも呼び出した側の束縛関係が 適用されます。Emacs Lisp で試してみると良いでしょう。

letrec の話
letrec では、 ある let clause で宣言された変数名が他の全ての clause の式の部分も scope としてみなします。 var-kの変数名が有効なのは、 以下の範囲になります。
	(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 が動くわけではありません。

letrec の用いられる例としてよくいわれるのは、(相互)再帰をもちいた program です。 lambda 式 などをもちいて書かれます。

(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 の中に実現することが出来るわけです。

(*): Scheme と 代入
Scheme においては実は変数に対する代入が存在します。 このため、実際には変数とその値との間に store (後述) が存在します。

関数型言語 と メモリ
ところで、関数型言語で store は実現できるのでしょうか? store は、場所と値への関数と考えることができます。 そこで、ユーザが store を用いたい関数すべてを、 その引数と結果に store 関数が加えられた高階関数としてやることで、 store を実現することが、原理的に可能です。
また、関数型の世界でメモリを扱うことの出来る枠組として monad が知られています。

99.10.6/ Tomio KAMADA: kamada@cs.kobe-u.ac.jp