我不确定我是否正确理解,为什么在旧版本的 Lisp 中没有实现静态作用域,只有动态作用域。发明Scheme的Sussman和Guy L. Steele Jr.只实现了Scheme的静态范围。
我发现有时静态变量使用起来更方便,因为它们可以用作完美的状态持有者,尽管我们应该小心避免不希望的名称冲突,因为这是不希望的副作用。
我知道静态范围是在编译时检测到的,而动态范围仅在运行时检测到。而动态范围被认为是难以欺骗的,有时甚至是推理的。
如果我们把上面提到的事实放在一边,我不确定我是否理解为什么静态范围通常被认为比动态范围更好?
动态作用域的基本问题是它不是组合的,因此违反了抽象。特别是,一段代码(例如,一个函数)的行为通常取决于从何处调用它,以及在调用者的站点上可见的定义。因此,调用方必须小心不要定义与被调用方使用的(非本地)名称冲突的名称。因此,调用方必须知道每个被调用方的实现详细信息。这使得模块化变得糟糕。特别是,对函数实现的更改可能会中断所有调用方。
词法范围对程序员来说更好,因为它使他们能够更好地推理他们的程序。特别是,他们可以从程序的源代码(本地)而不是从其动态行为中推断程序。
此外,还可以使用动态绑定功能(如参数)向语言添加受约束形式的动态作用域。SRFI-39是该计划的一般提案。另请参阅球拍指南中的参数。这使您可以在词法范围的语言中获得动态范围的大部分好处(这是更好的默认值)。
考虑以下定义:
(define (make-adder n)
(lambda (x) (+ x n)))
现在,当你调用(make-adder 2)
时,你会得到一个函数。 这个函数有什么作用? 一起来看看:
(let ((add2 (make-adder 2)))
(let ((n 10))
(add2 n)))
;=> ?
上面的代码将计算出什么? 这取决于范围规则:
- 在词法范围的方案中,结果将为 12,因为
make-adder
定义中的n
与示例代码中的n
不同。 - 在(假设的)动态范围方案中,结果将为 20,因为在调用
+
时n
已反弹到10
。
现在,这两种行为都遵循简单、可预测的规则。 但请注意,在词法范围内的情况下,您可以查看make-adder
的定义,并能够将n
引用与其声明相关联,而无需知道将使用make-adder
的上下文。 在动态范围的情况下,情况并非如此。 (事实上,在动态范围下,make-adder
的参数是完全多余的。
正因为如此,在推理make-adder
的行为时,词汇范围是一个优势,这就是为什么它通常(但并非总是)是首选的原因。