方案环境模型关闭问题

时间:2015-10-10 16:29:37

标签: scheme eval apply sicp

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的句子令牌

  

通过构造一个框架,将过程的形式参数绑定到调用的参数,然后在构造的新环境的上下文中评估过程的主体,将过程对象应用于一组参数。新框架具有作为其封闭环境的应用程序对象的环境部分。

根据此规则,在f1内调用f3时,会创建一个新环境,其封闭环境是全局的,而不是在调用时创建的环境(调用E1} f2 1}}。对我来说,E1应该是封闭的环境。

为简洁起见,我画了两张描绘环境的照片 env when define functions env when invoke f3

类似的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

我有什么遗漏的吗?或为什么f3Scheme中的C打印1而不是2?

2 个答案:

答案 0 :(得分:3)

考虑到在环境模型中,当您定义一个函数时,实际上您获得了一个闭包,这是一对(函数,环境,用于评估 free 函数的变量)。自由变量值是函数中提到的变量,它不是参数或局部变量,因此,当实际执行函数时,它将在闭包的环境部分中进行搜索。

因此,当您定义f1时,返回的闭包具有当前全局环境作为环境,其中a1具有值1

f2的定义中,您将a1定义为2。这是本地定义,因此在正文中,首先在本地环境中搜索a1的值,然后找到2

f3的定义中,您再次将a1定义为2,并且此绑定再次出现在本地环境中,但您调用f1,即a1一个闭包,在执行期间,根据f1的定义搜索1的值(找到的值是在闭包建筑中使用的环境中出现的值)时间,即2。)

这种解释变量的方式称为静态绑定,与动态绑定形成对比,其中结果为f3

请注意,C和Scheme都使用静态绑定,而您的C示例正好显示:调用1的结果为f1,因为这是f1内打印的值。另一方面,图像不正确。您应该将环境视为一组帧,每个帧都包含连接到其他帧的绑定(即耦合变量,当前值)。因此f2f3E1的关闭是不同的。

下图显示了全球环境的增长:

enter image description here

a1E2定义后的全球环境。在f1定义之后的f1,您可以注意到f1的值是指向第二帧的闭包(这允许递归定义,因为a1可以在原则称自己)。在闭包中,E1的值是E3中的值。 f2是定义a1之后的环境,其中闭包首先指向本地环境,其中2等于E4,然后指向当前的全局环境。最后f3a2定义后的全球环境。请注意,新闭包还有一个2等于f3的本地环境。

调用f1时,f1内的E2将被检索为f1a1 = 1的值,并且在评估f3时,使用(define a1 2)

另一方面,使用动态绑定,不需要创建闭包。在当前环境中评估每个函数,该环境是使用参数绑定和本地定义扩展的全局环境。所以你可以想象全球环境现在只有这种形式:

enter image description here

但在评估(f1)时,f1会向环境添加新框架:

enter image description here

并使用此环境评估最后一个表单a2。在此评估期间,在环境中检索2,并在相同(唯一)环境中再次评估 正文,其中%cprintf

答案 1 :(得分:1)

这里棘手的部分是f2中的内部定义。

如果您尝试使用set!代替define的此计划 你会得到你期望的结果(为了清楚起见,我改名为最后一个 功能到f3)。

(define a1 1)
(define (f1) a1)
(f1) ; return 1
(define (f2) (set! a1 2) a1)
(f2) ; return 2
(define (f3) (define a1 2) (f1))
(f3) ; return 1,not 2

输出:

1
2
2

现在您没有看到原始程序中的行为是由于的原因 内部定义:

 (define (f2) (define a1 2) a1)

内部定义将扩展为

 (define (f2) 
   (letrec ((a1 2))
     a1))

如果我们重命名变量是:

 (define (f2) 
   (letrec ((a2 2))
     a2))

因此内部定义将分配一个新变量,因此a1绑定的原始位置不受影响 - 因此保持值为1。

注意:在REPL(顶级)中使用define定义已定义的变量等同于`set!。这是顶级定义,内部定义是两个独立的概念。