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