Lisp 为什么静态作用域通常被认为比动态作用域更好?

Lisp 为什么静态作用域通常被认为比动态作用域更好?,lisp,scheme,Lisp,Scheme,我不确定我是否理解正确,为什么在旧版本的Lisp中没有实现静态作用域,只有动态作用域。发明Scheme的Sussman和Guy L.Steele Jr.只实现了Scheme的静态范围 我发现有时静态变量使用起来更方便,因为它们可以用作完美的状态保持器,尽管我们应该小心避免不希望出现的名称冲突,这是不希望出现的副作用 我知道静态作用域是在编译时检测到的,而动态作用域只在运行时检测到。动态范围界定被认为是难以理解的,有时甚至难以推理 如果我们抛开上述事实不谈,我不确定我是否理解为什么静态范围界定通常

我不确定我是否理解正确,为什么在旧版本的Lisp中没有实现静态作用域,只有动态作用域。发明Scheme的Sussman和Guy L.Steele Jr.只实现了Scheme的静态范围

我发现有时静态变量使用起来更方便,因为它们可以用作完美的状态保持器,尽管我们应该小心避免不希望出现的名称冲突,这是不希望出现的副作用

我知道静态作用域是在编译时检测到的,而动态作用域只在运行时检测到。动态范围界定被认为是难以理解的,有时甚至难以推理


如果我们抛开上述事实不谈,我不确定我是否理解为什么静态范围界定通常被认为比动态范围界定更好?

动态范围界定的根本问题是它不是组合的,因此违反了抽象。特别是,一段代码的行为,比如说,一个函数通常取决于调用它的位置,以及调用方站点上可见的定义。因此,调用方必须小心,不要定义与被调用方使用的非本地名称冲突的名称。因此,调用者必须知道每个被调用者的实现细节。这导致了可怕的模块化。特别是,对函数实现的更改可能会中断所有调用者。

动态作用域的基本问题是它不是组合的,因此违反了抽象。特别是,一段代码的行为,比如说,一个函数通常取决于调用它的位置,以及调用方站点上可见的定义。因此,调用方必须小心,不要定义与被调用方使用的非本地名称冲突的名称。因此,调用者必须知道每个被调用者的实现细节。这导致了可怕的模块化。特别是,对函数实现的更改可能会中断所有调用者。

词法作用域对程序员来说更好,因为它使他们能够更好地对自己的程序进行推理。特别是,他们可以从程序的本地源代码而不是动态行为来推断程序


此外,还可以使用动态绑定功能(如参数)向语言添加受约束形式的动态作用域。这是该计划的总体建议。另请参见球拍指南中的。这使您能够在词汇范围语言中获得动态作用域的大部分好处,这是更好的默认设置。

词汇作用域更适合程序员,因为它使他们能够更好地对自己的程序进行推理。特别是,他们可以从程序的本地源代码而不是动态行为来推断程序


此外,还可以使用动态绑定功能(如参数)向语言添加受约束形式的动态作用域。这是该计划的总体建议。另请参见球拍指南中的。这可以让您在词汇范围语言中获得动态范围的大部分好处,这是更好的默认设置。

考虑以下定义:

(define (make-adder n)
  (lambda (x) (+ x n)))
现在,当你调用makeadder 2时,你会得到一个函数。这个函数做什么?我们来看看,

(let ((add2 (make-adder 2)))
  (let ((n 10))
    (add2 n)))
;=> ?
上述代码的计算结果是什么?这取决于范围界定规则:

在词汇范围的方案中,结果将是12,因为make加法器定义中的n与示例代码中的n不同。 在一个假设的动态范围方案中,结果将是20,因为在调用+时n已反弹到10。 现在,这两种行为都遵循简单、可预测的规则。但请注意,在词汇范围内的情况下,您可以查看makeadder的定义,并能够将对n的引用与其本地声明关联起来,而不知道makeadder将在其中使用的上下文。在动态范围的情况下,情况并非如此。事实上,在动态范围下,使加法器成为加法器的论点是完全多余的


因此,在对make adder的行为进行推理时,词汇作用域是一个优势,这就是为什么它通常是首选的,但并不总是首选的原因。

考虑以下定义:

(define (make-adder n)
  (lambda (x) (+ x n)))
现在,当你调用makeadder 2时,你会得到一个函数。这个函数做什么?我们来看看,

(let ((add2 (make-adder 2)))
  (let ((n 10))
    (add2 n)))
;=> ?
上述代码的计算结果是什么?这取决于范围界定规则:

在词汇范围的方案中,结果将是12,因为make加法器定义中的n与示例代码中的n不同。 在一个假设的动态范围方案中,结果将是20,因为在调用+时n已反弹到10。 现在,这两种行为都遵循简单、可预测的规则。但请注意,在词汇范围内的情况下,您可以查看makeadder的定义,并能够将对n的引用与其本地声明关联起来,而不知道makeadder将在其中使用的上下文。这在dyna中是不正确的 显微镜范围的案件。事实上,在动态范围下,使加法器成为加法器的论点是完全多余的


因此,在对make adder的行为进行推理时,词法作用域是一个优势,这就是为什么它通常是但并不总是首选的原因。

请参见以下问题和答案:“发明Scheme的Abelson和Sussman只对Scheme实施了动态作用域”。这句话基本上都是假的。雷纳,你是对的。Scheme,只有静态作用域。尝试证明程序正确,答案将非常清楚。请参阅此问题和答案:“发明Scheme的Abelson和Sussman只在Scheme中实现了动态作用域”。这句话基本上都是假的。雷纳,你是对的。Scheme,只有静态作用域。试着证明程序是正确的,答案会非常清楚。Matthias,谢谢你这个雄辩的例子。你认为,如果存在某种假设的情况,动态范围的代码会远远优于词汇范围的代码吗?@jjpcondor是的,在某些情况下,动态范围非常有用。例如,在支持词法和动态变量的Common Lisp中,动态范围通常用于上下文相关设置。您可以编写类似于let*print base*16 do stuff的代码,并让do stuff在base 16中完成所有数字输出。或者您可以重新绑定*标准输出*以指向一个文件,该文件将所有在相应动态范围内没有明确目标的输出重定向到该文件中,有点像在Unixoid操作系统中通过>重定向标准输出。Matthias,感谢您提供了这个生动的示例。你认为,如果存在某种假设的情况,动态范围的代码会远远优于词汇范围的代码吗?@jjpcondor是的,在某些情况下,动态范围非常有用。例如,在支持词法和动态变量的Common Lisp中,动态范围通常用于上下文相关设置。您可以编写类似于let*print base*16 do stuff的代码,并让do stuff在base 16中完成所有数字输出。或者,您可以重新绑定*标准输出*以指向一个文件,该文件将在相应的动态范围中没有明确目标的所有输出重定向到该文件中,有点像在Unixoid操作系统中通过>重定向标准输出。