原文地址:http://blog.youxu.info/2010/07/12/scheme-1/
完整内容请看原文:
。。。。。
---------------------------------前面省略部分内容------------------------------
两种作用域
为了说明自由变量的幽灵和作用域,我们还是从一个例子入手。假设我们要一个做加 n 的函数。为了体现出自由变量,我们把它写成
(define (addn s) ( lambda x (+ x s)))
这个函数本身没什么特别的:输入一个 s, 输出一个 对任意 x 返回 x+s 的函数。注意到这个函数的“返回值”是一个函数 。 基于这个 addn 函数,我们可以定义 +1 函数 add1 函数如下,
(define (add1 s) ((addn 1) s))
这个也很好解释,如果输入一个 s, (addn 1)
返回了一个加一函数,这个函数作用在 s 上,即可得到 s+1。一切看上去很顺利,直到我们用一个Scheme 出现前的 LISP 解析器去计算 (add1 4)
。 我们期望得到的值是 5, 而它给你的值可能是 8。怎么回事?
为了解释这个 8 的来源,我们可以模拟一下一个基于栈的解释器的工作过程。(add1 4)
调用首先将参数 s 赋值为 4 然后,展开 add1 函数,即将 s=4 压栈,计算 (addn 1)
。在调用 addn 时。s 又作为了 addn 的形式参数。因此,按照基于栈的解释器的标准做法,我们在一个新的活动窗口中将 s =1 压栈。addn 这个函数返回的是一个 “lambda x (+ x s)
” 的函数,其中 s 是自由变量。 然而一旦 addn 返回,栈中的 s=1 就会被弹出。当我们把这个返回了的 lambda 表达式作用到 4 上求值时候,x 是这个 lambda 表达式传入的形式参数,赋值为 4,栈里面的 s 的值 只有 s=4, 因此 (+ x s) 得到的是 8。
这显然不是我们想要的。总结这个结果错了的原因,是因为我们的解释器没有限定 lambda x (+ x s) 里面的自由变量 s 为 1。 而是在计算这个 lambda 表达式的时候才去查找这个自由变量的值。 自由变量的幽灵在函数上开了一个后门,而我们没有在我们想要的地方堵上它,让它在函数真正求值的时候泄漏出来。
我们不是第一个发现这个问题的人。 实际上, LISP 刚出来没多久,就有人向 LISP 的发明人 John McCarthy 报告了这个 “BUG”。 John 也认为这是一个小 BUG,就把球踢给了当时写 LISP 实现的 Steve Russell。此人我之前的文章介绍过,乃是一个水平很高的程序猿(Code Monkey)。他认识到,这个问题的来源,在于返回的 lambda 表达式失去了不应该失去的确定它自由变量值的环境信息,在求值的时候,这些环境信息应该跟着这个 lambda 表达式一起。这样才能保证没有这个 BUG。不过 lambda 表达式在 LISP 语言中已经成型了,所以他就引入了一个新叫做 FUNCTION 的修饰符。作为参数的 lambda 表达式或函数要改写成 (FUNCTION lambda) 。 这样,这个 lambda 表达式在被 eval 解析的时候就会被标记成 FUNARG,并且静态绑定到解析时所在环境。而用 APPLY 对函数求值时,有 FUNARG 标签的函数会在当时绑定的环境中求值,而不是在当前环境中求值。自由变量没有到处乱跑,而是被限制在了当时绑定的环境里面。 Russell 的这个巧妙设计,成功关闭了自由变量在函数上开的口。这种加上了环境的函数就既能够被四处传递,而不需要担心自由变量的幽灵到处乱串。 这个东西,后来就被称为“闭包”。Russell 用 FUNCTION,以用一种“装饰”的方式,在 LISP 1.5 中第一次引入和实现和闭包。
在编程语言的术语中,上面的让自由变量自由自在的在运行时赋值的机制,一般叫做动态作用域(dynamic scope),而让函数和确定自由变量值在解析时静态绑定的机制,一般称之为静态作用域(static dynamic scope)。既然是静态绑定的环境是解析的时候确定的,而解析器是逐行解析程序的,所以,静态作用域的环境是完全由程序代码的结构确定的。因此有时候静 态作用域又被等价的称为“文法作用域”(lexical scope)。上面我们的例子里。我们的真正意图是使用静态作用域,却遇到了一个使用动态作用域的 LISP 解析器,因此出现了 (add1 4) 等于 8 的错误。 但这个问题并不足以说明静态作用域一定好。动态作用域的问题,关键在于违反了 Alpha 变换原则和封装原则,不过不在此详细展开了。
------------------------------------------分割线--------------------------------
。。。。。。。。。