October 17, 2006

let, let* 和 letrec(转)

使用 let, let*, letrec 都可以在当前环境中构造局部变量。这种 变量的生命会延续到这个环境消失为止。

这就像 C 语言里的

{  int x = 10;
int y = 20;

foo(x,y);
}

但是有一点不同就是,Scheme 的 let 生成的环境是分配在堆里而不 是像 C 那样分配在栈里的。所以 let 的局部变量有可能在 let 的 block 执行完毕以后还继续存在,只要有某些东西引用到它们。

这样我们可以制造一些返回函数的函数,这些函数拥有自己的状态记 忆,而这些记忆并不是全局变量,它们有点像 C 函数的 static 变 量。

下面是几个例子:

(define (function-gen n)
(let ((local-var 0))
(lambda ()
(display "The local-var is ")
(display local-var)
(newline)
(set! local-var (+ 1 local-var)))))

(define f1 (function-gen 0))
(define f2 (function-gen 100))

(f1)
(f2)

函数 function-gen 接受一个参数 n,并且把它保存到自己的局部变 量 local-var. 它返回一个新的函数,这个函数被调用就会打印 local-var 的值,并且把 local-var 的值加 1.

我们用 0 和 100 作为参数传递给 function-gen,生成了两个函数 f1 和 f2. 这是两个起点不同的计数器。f1 从 0 开始,而 f2 从 100 开始。每次被调用两个函数都打印自己的数字,并且加 1.

可见,f1 和 f2 所见到的 local-var 是两个不同的空间。也就是说, 每次调用 function-gen,都会由 let 生成一个新的变量 local-var, 这个变量将一直伴随新生成的函数。

注意 let 里的 binding 是这样产生的。首先,进入 let 时,我们 只看到外层的绑定,然后每个 let 绑定的右边被 eval,然后这些值 被放到临时的一些空间,所有的右边都求值完毕后,这些值被一一赋 给左边的名字。

如果我们的代码不是那么简单,我们在 let 里生成了一个函数。比 如这样:

(define (func-gen)
(let ((x 10) (y 20))
(lambda (a b)
(+ x y a b))))

(define bar (func-gen))

func-gen 函数中被调用时,它在 let 空间中生成了一个函数,并且 作为 func-gen 的返回值送到外层,它被绑定到最外层的环境中的 bar 变量。那么这个函数引用了这个环境,这个 let frame 不会被 回收。

bar 如果在外层层环境被调用,那么它的名字绑定环境仍然是 let 里面的环境。也就是说,它仍然可以使用局部变量 x 和 y!

如果我们调用

(f 1 2)

就得到结果 33.

这相当于同时赋值。

所以在下面这种情况里,内层的 let 绑定 b 时,实际上使用的是外 层的 x 在计算。

(let ((x 10)    ; bindings of x
(a 20))) ; and a
+----------------------------------------------------------+
| (foo x) scope of outer x |
| (let ((x (bar)) and a |
| (b (baz x x))) |
| +------------------------------------------------+ |
| | (quux x a) scope of inner x | |
| | (quux y b) and b | ) |
| +------------------------------------------------+ |
| (baz x a) |
| (baz x b) |
+----------------------------------------------------------+

No comments: