通过代码里的注释,你也许已经可以理解它在做什么。我们先对表达式 e1 求值,得到 v1。然后我们把 (x . v1) 扩充到环境里,这样 (let ([x e1]) ...) 内部都可以看到 x 的值。然后我们使用这个扩充后的环境,递归调用解释器本身,对 let 的主体 e2 求值。它的返回值就是这个 let 绑定的值。
Lexical Scoping 和 Dynamic Scoping下面我们准备谈谈函数定义和调用。对函数的解释是一个微妙的问题,很容易弄错,这是由于函数体内也许会含有外层的变量,叫做“自由变量”。所以在分析函数的代码之前,我们来了解一下不同的“作用域”(scoping)规则。
我们举个例子来解释这个问题。下面这段代码,它的值应该是多少呢?
(let ([x 2]) (let ([f (lambda (y) (* x y))]) (let ([x 4]) (f 3)))) 在这里,f 函数体 (lambda (y) (* x y)) 里的那个 x,就是一个“自由变量”。x 并不是这个函数的参数,也不是在这个函数里面定义的,所以我们必须到函数外面去找 x 的值。
我们的代码里面,有两个地方对 x 进行了绑定,一个等于2,一个等于4,那么 x 到底应该是指向哪一个绑定呢?这似乎无关痛痒,然而当我们调用 (f 3) 的时候,严重的问题来了。f 的函数体是 (* x y),我们知道 y 的值来自参数 3,可是 x 的值是多少呢?它应该是2,还是4呢?
在历史上,这段代码可能有两种不同的结果,这种区别一直延续到今天。如果你在 Scheme (Racket)里面写以上的代码,它的结果是6。
;; Scheme (let ([x 2]) (let ([f (lambda (y) (* x y))]) (let ([x 4]) (f 3)))) ;; => 6 现在我们来看看,在 Emacs Lisp 里面输入等价的代码,得到什么结果。如果你不熟悉 Emacs Lisp 的用法,那你可以跟我做:把代码输入 Emacs 的那个叫 *scratch* 的 buffer。把光标放在代码最后,然后按 C-x C-e,这样 Emacs 会执行这段代码,然后在 minibuffer 里显示结果:
Lexical Scoping 和 Dynamic Scoping下面我们准备谈谈函数定义和调用。对函数的解释是一个微妙的问题,很容易弄错,这是由于函数体内也许会含有外层的变量,叫做“自由变量”。所以在分析函数的代码之前,我们来了解一下不同的“作用域”(scoping)规则。
我们举个例子来解释这个问题。下面这段代码,它的值应该是多少呢?
(let ([x 2]) (let ([f (lambda (y) (* x y))]) (let ([x 4]) (f 3)))) 在这里,f 函数体 (lambda (y) (* x y)) 里的那个 x,就是一个“自由变量”。x 并不是这个函数的参数,也不是在这个函数里面定义的,所以我们必须到函数外面去找 x 的值。
我们的代码里面,有两个地方对 x 进行了绑定,一个等于2,一个等于4,那么 x 到底应该是指向哪一个绑定呢?这似乎无关痛痒,然而当我们调用 (f 3) 的时候,严重的问题来了。f 的函数体是 (* x y),我们知道 y 的值来自参数 3,可是 x 的值是多少呢?它应该是2,还是4呢?
在历史上,这段代码可能有两种不同的结果,这种区别一直延续到今天。如果你在 Scheme (Racket)里面写以上的代码,它的结果是6。
;; Scheme (let ([x 2]) (let ([f (lambda (y) (* x y))]) (let ([x 4]) (f 3)))) ;; => 6 现在我们来看看,在 Emacs Lisp 里面输入等价的代码,得到什么结果。如果你不熟悉 Emacs Lisp 的用法,那你可以跟我做:把代码输入 Emacs 的那个叫 *scratch* 的 buffer。把光标放在代码最后,然后按 C-x C-e,这样 Emacs 会执行这段代码,然后在 minibuffer 里显示结果: