巴比伦娱乐场
官网
Portraits
Journal
Contact
结果是12!如果你把代码最内层的 x 绑定修成其它的值,输出会随之改变。
奇怪吧?Scheme 和 Emacs Lisp,到底有什么不一样呢?实际上,这两种看似差不多的 “Lisp 方言”,采用了两种完全不同的作用域方式。Scheme 的方式叫做 lexical scoping (或者 static scoping),而 Emacs 的方式叫做 dynamic scoping。
那么哪一种方式更好呢?或者用哪一种都无所谓?答案是,dynamic scoping 是非常错误的做法。历史的教训告诉我们,它会带来许许多多莫名其妙的 bug,导致 dynamic scoping 的语言几乎完全没法用。这是为什么呢?
原因在于,像 (let ((x 4)) …) 这样的变量绑定,只应该影响它内部“看得见”的 x 的值。当我们看见 (let ((x 4)) (f 3)) 的时候,并没有在 let 的内部看见任何叫“x” 的变量,所以我们“直觉”的认为,(let ((x 4)) …) 对 x 的绑定,不应该引起 (f 3) 的结果变化。
然而对于 dynamic scoping,我们的直觉却是错误的。因为 f 的函数体里面有一个 x,虽然我们没有在 (f 3) 这个调用里面看见它,然而它却存在于 f 定义的地方。要知道,f 定义的地方也许隔着几百行代码,甚至在另外一个文件里面。而且调用函数的人凭什么应该知道, f 的定义里面有一个自由变量,它的名字叫做 x?所以 dynamic scoping 在设计学的角度来看,是一个反人类的设计 :)
相反,lexical scoping 却是符合人们直觉的。虽然在 (let ((x 4)) (f 3)) 里面,我们把 x 绑定到了 4,然而 f 的函数体并不是在那里定义的,我们也没在那里看见任何 x,所以 f 的函数体里面的 x,仍然指向我们定义它的时候看得见的那个 x,也就是最上面的那个 (let ([x 2]) ...),它的值是 2。所以 (f 3) 的值应该等于 6,而不是12。
对函数的解释为了实现 lexical scoping,我们必须把函数做成“闭包”(closure)。闭包是一种特殊的数据结构,它由两个元素组成:函数的定义和当前的环境。我们把闭包定义为一个 Racket 的 struct 结构:
(struct Closure (f env)) 有了这个数据结构,我们对 (lambda (x) e) 的解释就可以写成这样:
[`(lambda (,x) ,e) (Closure exp env)] 注意这里的 exp 就是 `(lambda (,x) ,e) 自己。
有意思的是,我们的解释器遇到 (lambda (x) e),几乎没有做任何计算。它只是把这个函数包装了一下,把它与当前的环境一起,打包放到一个数据结构(Closure)里面。这个闭包结构,记录了我们在函数定义的位置“看得见”的那个环境。稍候在调用的时候,我们就能从这个闭包的环境里面,得到函数体内的自由变量的值。
对调用的解释
官网
Portraits
Journal
Contact