为什么Scheme允许在闭包中突变到封闭环境?

时间:2012-11-14 00:57:50

标签: scheme

以下方案代码

(let ((x 1))
   (define (f y) (+ x y))
   (set! x 2)
   (f 3) )

评估为5而不是4.考虑到Scheme促进静态范围,这是令人惊讶的。允许后续突变影响闭包环境中的绑定似乎恢复了有点动态范围。是允许的任何特定原因?

编辑:

我意识到上面的代码不太明显,无法揭示我所担心的问题。我在下面放了另一个代码片段:

(define x 1)

(define (f y) (+ x y))

(set! x 2)

(f 3)  ; evaluates to 5 instead of 4

4 个答案:

答案 0 :(得分:4)

这里有两个令你困惑的想法:通过内存进行范围界定和间接寻址。词法范围可以保证对x的引用始终指向x绑定中let的绑定。

您的示例中未违反此规定。从概念上讲,let绑定实际上是在内存中创建一个新位置(包含1),该位置是绑定到x的值。取消引用该位置后,程序将查找该内存位置的当前值。使用set!时,它会在内存中设置值。只有能够访问绑定到x的位置(通过词法范围)的各方才能访问或改变内存中的内容。

相比之下,动态范围允许任何代码更改您在f中引用的值,无论您是否允许访问绑定到x的位置。例如,

(define f
  (let ([x 1])
    (define (f y) (+ x y))
    (set! x 2)
    f))

(let ([x 3]) (f 3))

会在具有动态范围的虚构Scheme中返回6

答案 1 :(得分:3)

允许这种突变优秀。它允许您定义具有内部状态的对象,只能通过预先安排的方式访问:

(define (adder n)
  (let ((x n))    
    (lambda (y)
      (cond ((pair? y) (set! x (car y)))
            (else (+ x y))))))

(define f (adder 1))
(f 5)                 ; 6
(f (list 10))
(f 5)                 ; 15

除了通过x函数及其已建立的协议之外,无法更改f - 正是因为在Scheme中使用词汇作用域。

x变量是指属于let 的内部环境框架中的内存单元格,其中定义了内部lambda - 从而返回lambda及其定义环境的组合,也称为“封闭”。

如果你没有提供改变这个内部变量的协议,那么没有什么可以改变它,因为它是内部,我们早就离开了定义范围:

(set! x 5) ; WRONG: "x", what "x"? it's inaccessible!

编辑:你的新代码完全改变了你的问题的含义,那里也没有问题。就像我们仍然在定义环境中一样,所以内部变量自然仍然可以访问。

更多问题是以下

(define x 1)
(define (f y) (+ x y))
(define x 4)
(f 5) ;?? it's 9.

我希望第二个定义不会干扰第一个,但R5RS表示define与顶层的set!类似。

Closures用它们打包定义环境。 始终可以访问顶级环境。

x引用的变量f位于顶级环境中,因此可以从同一范围内的任何代码访问。也就是说,任何代码。

答案 2 :(得分:1)

不,这不是动态范围。请注意,此处的define是内部定义,只能访问let中的代码。具体而言,f未在模块级别定义。所以没有泄露出来。

内部定义在内部实施为letrec(R5RS)或letrec*(R6RS)。因此,它被视为相同(如果使用R6RS语义):

(let ((x 1))
  (letrec* ((f (lambda (y) (+ x y))))
    (set! x 2)
    (f 3)))

答案 3 :(得分:1)

我的答案显而易见,但我不认为其他任何人已经触及过它,所以让我说出来:是的,这是可怕的。你在这里真正观察到的是,突变使你很难推断你的程序将要做什么。纯函数代码 - 没有变异的代码 - 在使用相同的输入调用时总是产生相同的结果。具有状态和变异的代码不具有此属性。可能是使用相同的输入调用函数两次将产生单独的结果。