原文地址: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
变换原则和封装原则,不过不在此详细展开了。
------------------------------------------分割线--------------------------------
。。。。。。。。。
分享到:
相关推荐
【编程珠玑番外篇-G. 程序员心底的小声音】这篇文章主要探讨了程序员在编程学习和成长过程中的不同阶段以及如何从中级水平晋升到高级水平的问题。作者将程序员分为新手、高手和中手三个层次,并指出,中手程序员通常...
《编程珠玑番外篇》是由Google工程师徐宥创作的一系列关于编程语言和技术开发的深度思考文集。这个压缩包包含13篇PDF格式的文章,每一篇都蕴含着丰富的编程智慧,对于程序员和软件开发者来说,是不可多得的学习资料...
《编程珠玑》是计算机科学领域的一本经典著作,作者是Jon Bentley。这本书以其深入浅出的方式探讨了程序设计的艺术,特别关注了算法优化和问题解决的策略。它不仅仅是一本关于编程技巧的书,更是一本提升程序员思维...
2nd edition",书中涵盖了多种编程语言,但更侧重于解决问题的思路和方法,而非特定语言的语法细节。 该书的核心知识点包括: 1. **问题解决策略**:书中介绍了一系列用来解决编程问题的策略,如分治法、动态规划...
编程珠玑.pdf 编程珠玑.pdf 编程珠玑.pdf 编程珠玑.pdf
《编程珠玑》是一本经典的计算机科学与编程书籍,作者是Jon Bentley。这本书以其独特的视角深入探讨了程序设计的艺术和技巧,旨在提升程序员的问题解决能力,优化算法,并提高代码效率。书中涵盖了一系列实用的编程...
5. **编程语言新趋势**:探讨了当时的新技术,如Java和C++,以及后来的动态语言如Python和Ruby,反映了编程语言的发展变迁。 6. **性能优化**:深入讲解如何通过编译器优化、代码重构和硬件特性来提升程序性能。 ...
"源代码" 文件通常包含C、C++、Java或其他编程语言编写的程序,这些程序是为了实现《编程珠玑》书中提到的各种算法和数据结构。源代码的学习对于理解书中的概念至关重要,因为它们提供了实际操作的例子,读者可以...
《编程珠玑(中文版)》是一本经典的计算机科学著作,它作为《编程珠玑》系列的一部分,深入探讨了一系列对程序员非常重要的主题。这些知识点涵盖了算法、数据结构、编程技巧以及软件工程实践等方面。 #### 重要...
《编程珠玑(续)》是计算机科学方面的经典名著《编程珠玑》的姊妹篇,讲述了对于程序员有共性的知识。书中涵盖了程序员操纵程序的技术、程序员取舍的技巧、输入和输出设计以及算法示例,这些内容组成一个有机的整体,...
编程珠玑-[美]乔恩美特利.mobi、kindle文档、第二版修订版
1. 书名介绍:文件提到了《编程珠玑 续》这一标题,它暗示了这本书可能是作为先前的编程经典《编程珠玑》的续集或补充。这表明,读者可以期望从这本书中找到与前作类似的编程见解和技巧。 2. 内容描述:文件中提到...
《编程珠玑 第2版(修订版)》是一本深受程序员喜爱的经典著作,它不仅提供了丰富的编程实践经验,还深入探讨了程序设计的艺术与智慧。这本书的修订版更是在原版基础上进行了更新和完善,旨在帮助程序员提升编程技能,...
总的来说,《编程珠玑》这本书涵盖了编程实践中遇到的诸多问题,包括但不限于数据结构、算法、输入/输出优化等,通过实例解析,使读者能够掌握如何优雅地解决编程挑战。配合压缩包中的其他资源,读者不仅可以深入...
《编程珠玑》这本书主要关注的是如何解决实际问题,而不是单纯介绍编程语言或算法理论。书中通过大量的例子和案例研究,向读者展示了如何高效地编写代码以及如何优化算法性能。《编程珠玑》分为两个部分:第一部分...
《字串学珠玑》(Jewels.of.stringology)是一本深度探讨字符串处理算法的专著,由M. Crochemore和W. Rytter合著。这本书全面覆盖了字符串算法的重要领域,旨在为读者提供一个深入理解字符串处理技术的宝贵资源。...
《编程珠玑(第 2版·修订版)》是计算机科学方面的经典名著。书的内容围绕程序设计人员面对的一系列实际问题展开。作者JonBentley以其独有的洞察力和创造力,引导读者理解这些问题并学会解决方法,而这些正是程序员...
中文第二版-编程珠玑.pdf,非常有用的一本书。