首页 文章

方案环境模型关闭问题

提问于
浏览
2

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 应该是封闭的环境 .

为简洁起见,我绘制了两张描绘环境的图片
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

有什么我想念的吗?或者为什么 SchemeC 中的 f3 打印1而不是2?

2 回答

  • 1

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

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

    f2 的定义中,将 a1 定义为 2 . 这是一个本地定义,因此在正文中,首先在本地环境中搜索 a1 的值,并找到 2 .

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

    这种解释变量的方式称为静态绑定,与动态绑定相反,其中结果将是 2 .

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

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

    enter image description here

    E1a1 定义后的全局环境 . E2 定义 f1 之后,你可以注意到 f1 的值是一个指向第二帧的闭包(这允许递归定义,因为 f1 原则上可以调用自身) . 在闭包中, a1 的值是 E1 中的值 . E3f2 定义后的环境,其中闭包首先指向本地环境,其中 a1 等于 2 ,然后指向当前的全局环境 . 最后 E4f3 定义后的全球环境 . 请注意,新闭包再次具有 a2 等于 2 的本地环境 .

    当调用 f3 时,其体内的 f1 将在 E2 中作为 f1 的值被检索,并且当评估 f1 时,则使用 a1 = 1 .

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

    enter image description here

    但是当评估 f3 时, (define a1 2) 将新框架添加到环境中:

    enter image description here

    并使用此环境评估最后一个表单 (f1) . 在此评估期间,将在环境中检索 f1 ,并在同一(唯一)环境中再次评估其正文,其中 a2 的值为 2 .

  • 3

    这里棘手的部分是 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! . 这是顶级定义,内部定义是两个独立的概念 .

相关问题