SICP 3.2引入环境模型来取代替代模型 .
我在学习这部分时做了以下测试:
(define a1 1)
(define (f1) a1)
(f1) ; return 1
(define (f2) (define a1 2) a1)
(f2) ; return 2
(define (f3) (define a1 2) (f1))
(f3) ; return 1,not 2
最后一个表达超出了我的预期 .
来自SICP的句子令牌
通过构造框架,将过程的形式参数绑定到调用的参数,然后在构造的新环境的上下文中评估过程的主体,将过程对象应用于一组参数 . 新框架具有作为其封闭环境的应用程序对象的环境部分 .
根据此规则,在 f3
内调用 f1
时,将创建一个新环境,其封闭环境是全局的,而不是在调用 f2
时创建的环境(调用 E1
) . 对我来说, E1
应该是封闭的环境 .
为简洁起见,我绘制了两张描绘环境的图片
一个类似的C例子:
int a = 1;
void f1() {
printf("a = %d from f1\n", a);
}
void f2() {
int a = 2;
printf("a = %d from f2\n", a);
}
void f3() {
int a = 2;
f1();
}
int main() {
f2();
f3();
return 0;
}
print:
a = 2 from f2
a = 1 from f1
有什么我想念的吗?或者为什么 Scheme
和 C
中的 f3
打印1而不是2?
2 回答
考虑到在环境模型中,当您定义一个函数时,实际上您获得了一个闭包,即一对(函数,环境,用于评估函数的自由变量) . 自由变量值是函数中提到的变量,它不是参数或局部变量,因此,当函数实际执行时,它将在闭包的环境部分中进行搜索 .
因此,当您定义
f1
时,返回的闭包具有当前全局环境作为环境,其中a1
具有值1
.在
f2
的定义中,将a1
定义为2
. 这是一个本地定义,因此在正文中,首先在本地环境中搜索a1
的值,并找到2
.在
f3
的定义中,再次将a1
定义为2
,并且此绑定再次出现在本地环境中,但是您调用f1
,这是一个闭包,并且在其执行期间,根据f1
的定义搜索a1
的值 . (并且找到的值是在闭包构建时使用的环境中出现的值,即1
. )这种解释变量的方式称为静态绑定,与动态绑定相反,其中结果将是
2
.请注意,C和Scheme都使用静态绑定,您的C示例正好显示:调用
f3
的结果是1
,因为这是f1
中打印的值 . 另一方面,图像不正确 . 您应该将环境视为一组框架,每个框架都包含连接到其他框架的绑定(即耦合变量,当前值) . 这样f1
,f2
,f3
的关闭是不同的 .下图显示了全球环境的增长:
E1
是a1
定义后的全局环境 .E2
定义f1
之后,你可以注意到f1
的值是一个指向第二帧的闭包(这允许递归定义,因为f1
原则上可以调用自身) . 在闭包中,a1
的值是E1
中的值 .E3
是f2
定义后的环境,其中闭包首先指向本地环境,其中a1
等于2
,然后指向当前的全局环境 . 最后E4
是f3
定义后的全球环境 . 请注意,新闭包再次具有a2
等于2
的本地环境 .当调用
f3
时,其体内的f1
将在E2
中作为f1
的值被检索,并且当评估f1
时,则使用a1 = 1
.另一方面,使用动态绑定,不需要创建闭包 . 在当前环境中评估每个函数,该环境是使用参数绑定和本地定义扩展的全局环境 . 所以你可以想象全球环境现在只有这种形式:
但是当评估
f3
时,(define a1 2)
将新框架添加到环境中:并使用此环境评估最后一个表单
(f1)
. 在此评估期间,将在环境中检索f1
,并在同一(唯一)环境中再次评估其正文,其中a2
的值为2
.这里棘手的部分是
f2
中的内部定义 .如果您尝试使用
set!
而不是define
的程序,您将得到您期望的结果(为清楚起见,我将最后一个函数重命名为f3
) .输出:
现在您没有在原始程序中看到该行为的原因是由于内部定义:
内部定义将扩展为
如果我们重命名变量是:
因此内部定义将分配一个新变量,因此a1绑定的原始位置不受影响 - 因此保持值1 .
注意:在REPL(顶级)中使用
define
来定义已定义的变量,相当于`set! . 这是顶级定义,内部定义是两个独立的概念 .