SICP的求值和环境模型
求值规则
关于解释器如何求值一个组合式的问题,其整体描述仍然与我们在1.1.3节第一次介绍时完全一样。
在1.1.3节中的代换模型中,我们如果要对一个组合表达式求值,需要:
(1) 求值这一组合式里的各个子表达式。
(2) 将运算符(operator)子表达式的值应用于运算对象(operand)子表达式的值。
PS:赋值的存在给求值规则的步骤 (1) 引入了一个微妙问题,即以不同的顺序对组合式中各个子表达式求值,它们就会产出不同的值(想想在C语言中被
(++i)+(++i)
支配的恐惧[2])。然而,这种顺序应该看做是一个实现细节,我们永远不要去写依赖于特定顺序的程序。比如,如果一个复杂的编译器去做程序的优化,它完全可能改变其中各子表达式的求值顺序。
现在我们要用求值的环境模型代替求值的代换模型,在这一模型中我们将会讨论当定义一个复合过程以及当一个复合过程应用于实参究竟意味着什么。
我们来看一个例子。考虑在全局环境里求值下面的过程定义:
def square(x): return x * x
下图展示的是在全局环境中求值这一def
表达式而产生的环境结构:

这里的过程对象是一个序对(pair),其代码部分描述的是一个带有形参x
的过程,过程体是return x * x
。过程对象的环境部分是一个指向全局环境的指针(因为这个过程的定义是在全局环境中求值的)。这个定义在全局帧中加入了一个新绑定,将上述过程对象绑定于符号square
。一般而言,用def
建立定义的方式(Python的话用=
也可表示变量定义)就是将新的绑定加入到帧中。
这样,过程对象创建的环境模型可总结定为:
对于一个给定环境求值一个过程的定义,将创建起一个过程对象,这个过程对象是一个序对,由该过程的正文和一个指向环境的指针组成,这一指针指向的就是创建这个过程对象时的环境。
接下来我们来描述过程对象的应用。环境模型说明,将一个过程对象应用于一组实参时,将会建立起一个新的环境,其中包含了将所有形参绑定到对应实参的一个帧。该帧的外围环境就是创建该过程对象时的环境(在这个例子中即全局环境),随后就在这个新环境下求值该过程的体。
下面我们来演示这一规则的实施情况,下图展示了在全局环境里对表达式square(5)
求值而创建起来的环境结构,其中square
即上图中生成的过程。这一过程应用的结果是创建了一个新环境E1。这个环境从一个帧开始,帧中包含着将这个过程的形参x
约束到实参5。这个帧引出的指针说明这个帧的外围环境就是全局环境。现在我们要在E1里求值过程的体return x * x
。因为在E1里x
的值是5,所以求值结果是return 5 * 5
,也就是return 25
。

这样,过程对象的应用的环境模型可总结为:
将一个过程对象应用于一集实参,将造出一个新帧,其中将过程的形参绑定到调用时的实参,而后在构造起的这一新环境的上下文中求值过程体。这个新帧的外围环境就是创建该过程对象时的环境(这个例子中即全局环境)。
PS:所谓定义一个符号(包括用
def foo()
定义过程或用foo = 1
定义变量),也就是在当前环境frame里建一个绑定,并赋予这个符号指定的值。而赋值运算=
则会要求我们首先在环境中确定有关变量的绑定位置,然后再修改这个绑定,使之表示为这个新值。这也就是说,首先需要找到包括这个变量绑定的第一个帧,然后修改这个帧。如果该变量在环境中没有绑定,赋值将报一个错误。当然由于Python语法的缘故,x = ...
可同时表示变量定义和赋值,故此处注意例外。
此外,众所周知,Python的基础数据类型(如整形、字符串等)是不可变(immutable)的,故对基础数据类型而言,所谓赋值运算其实就等同于我们前面说的拿符号去绑定新的对象)。