Scheme 为什么方案允许在封闭环境中对封闭环境进行变异?

Scheme 为什么方案允许在封闭环境中对封闭环境进行变异?,scheme,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

以下是方案代码:

(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

不,这不是动态范围界定。请注意,您的
define
这里是一个内部定义,只能由
let
中的代码访问。具体而言,
f
未在模块级定义。所以什么也没有泄露出去

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


这里有两个概念让你感到困惑:范围界定和通过记忆的间接性。词法范围保证对
x
的引用始终指向
let
绑定中
x
的绑定

在您的示例中没有违反这一点。从概念上讲,
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

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

(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
除非通过
f
函数及其已建立的协议,否则无法更改
x
——正是因为Scheme中的词法范围

x
变量是指内部环境框架中属于定义内部
lambda
let
的存储单元,因此返回
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.
我希望第二个定义不会干扰第一个定义,但是说
define
就像
set位于顶层

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


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

我的答案是显而易见的,但我认为没有其他人触及过它,所以让我说:是的,它很可怕。你在这里真正观察到的是,变异使得你很难对你的程序将要做什么进行推理。纯函数代码——没有变异的代码——在使用相同的输入调用时总是产生相同的结果。具有state和mutation的代码没有此属性。可能用相同的输入调用函数两次会产生不同的结果。

你说得对。这不是动态范围界定(我说的是“有点”,也许我应该说的是“似乎”),我关心的是允许这种行为是否好。至少我看不到任何积极的结果。您可以定义一个函数
f
,其中使用全局变量
x
。稍后您将
设置
x
并调用
f
,它会爆炸。在我看来,创建一个只关闭而不关闭商店的环境是一个糟糕的设计选择。好吧,我想我现在更好地理解你的问题了。关闭门店还有其他问题:如何确定要关闭多少门店?对于环境,从程序的文本中可以明显看出:只有范围中的引用。对于存储,如果
f
调用
g
f
是否必须关闭
g
访问的内存位置?另外,这将如何影响同时使用您关闭的相同内存位置的程序?我认为
f
不必关闭
g
可以访问的存储,即使
f
调用
g
,因为
g
已经关闭了它。我还没有考虑涉及并发的可能后果。我同意这个模拟对象特性是合理的。但是请注意,
set出现在
f
中。自然,它可以变异
x
(set!x 5)
此处将失败,但因为
x
加法器的局部变量,即它不在范围内。我已经更新了问题中的代码片段。你可以看到
set出现在
f
之外,但仍然可以变异在
f
的闭包中关闭的变量。我反对的就是这个用例。我认为仍然可以支持您举例说明的功能,并且不允许
设置从闭包外部到闭包中关闭的变量。我认为我反对将闭包中关闭的变量进行变异
(define x 1)
(define (f y) (+ x y))
(define x 4)
(f 5) ;?? it's 9.