3

以下方案代码

(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

4 回答 4

5

您在这里混淆了两个想法:作用域和通过内存的间接性。词法作用域保证你的引用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))

将返回6一个具有动态范围的虚构方案。

于 2012-11-14T04:09:10.660 回答
4

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

(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.

我希望第二个定义不会干扰第一个,但R5RSdefine就像set!在顶层一样。

闭包将它们的定义环境与它们打包在一起。顶级环境始终是可访问的。

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

于 2012-11-14T12:43:14.807 回答
1

不,它不是动态范围。请注意,您的define此处是一个内部定义,只有let. 具体来说,f不是在模块级别定义的。所以什么都没有泄露出去。

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

(let ((x 1))
  (letrec* ((f (lambda (y) (+ x y))))
    (set! x 2)
    (f 3)))
于 2012-11-14T01:12:13.077 回答
1

我的答案很明显,但我认为其他人没有触及它,所以让我说:是的,它很可怕。您在这里真正观察到的是,突变使您很难推断您的程序将要做什么。纯功能代码——没有突变的代码——在使用相同的输入调用时总是产生相同的结果。具有状态和突变的代码不具有此属性。使用相同的输入两次调用一个函数可能会产生不同的结果。

于 2012-11-14T16:06:43.210 回答