31

我了解到静态作用域是唯一明智的做事方式,而动态作用域是魔鬼的工具,仅由解释器/编译器的不良实现造成。

然后我从Common Lisp vs. Scheme文章中看到了这个片段:

根据标准,仅限词法和动态词法范围。
范围特殊变量。提供了常见的动态范围变量
Lisp 只是在这一点上获胜。通过一些实现作为扩展
                                  但是使用它们的代码是不可移植的。

     (我听说过关于是否动态范围的争论
      首先是或不是一个坏主意。我不在乎。
      我只是注意到你可以用它做你想做的事情
      没有它就不能轻易做到。)

为什么 Common Lisp “在这一点上获胜”?使用动态作用域更容易做哪些事情?我真的无法证明需要它/将其视为一件好事。

4

10 回答 10

39

像其他一切一样,动态范围只是一个工具。使用得当它可以使某些任务更容易。使用不当会引入错误和令人头疼的问题。

我当然可以看到它的一些用途。可以消除将变量传递给某些函数的需要。

例如,我可能会在程序开始时设置显示,并且每个图形操作都只是假设这个显示。

如果我想在该显示器内设置一个窗口,那么我可以将该窗口“添加”到以其他方式指定显示器的变量堆栈中,并且在此状态下执行的任何图形操作都将转到窗口而不是显示器作为所有的。

这是一个人为的例子,可以通过将参数传递给函数来同样好地完成,但是当您查看此类任务生成的一些代码时,您会意识到全局变量确实是一种更容易的方法,而动态范围为您提​​供了一个很多全局变量的健全性与函数参数的灵活性

-亚当

于 2008-11-26T15:20:55.450 回答
17

动态范围的主要风险是意外后果。动态作用域使作用域遵循运行时堆栈,这意味着作用域中的符号集要大得多,并且在任何符号使用时都不是很明显。动态范围的变量很像全局变量,只是每个变量可能有多个版本,只有最新的定义可见,而隐藏所有其他变量。

动态范围,只要它有用,它对于需要对运行时堆栈敏感的行为很有用。例如(一般来说,不是特定于 Lisp 或变体):

  • 异常处理 - 最顶层的 catch 块是发生异常时“在范围内”的那个
  • 安全性 - .NET 基于代码的安全性根据调用它的代码来决定某些特权 API 的可访问性。

将其用于其他用途的问题在于,它会在词汇上相距遥远的代码片段之间创建隐式依赖和耦合。这样,它也类似于全局变量,只是它可能更糟(由于动态覆盖的定义)。

于 2008-11-26T15:15:53.230 回答
9

动态范围在某些特定领域的语言中很有用。特别是,它可以方便地使用样式表语言。我的经验来自GNU TeXmacs样式表语言。

在这种语言中,显示参数存储在动态范围的变量中。这些变量会影响其作用域内每个原子的渲染,包括由作用域中调用的函数产生的原子。

TeXmacs 中的动态范围也用于标记交叉引用。用于交叉引用的锚点从其环境中获取标签。例如,公式块中包含的锚点将使用公式编号作为标签,而不是位于公式之后的锚点的节编号。

想想看,unix 环境变量也是动态作用域的变量。尽管内部作用域不能改变外部作用域中变量的值。

正如 Barry Kelly 所指出的,动态范围也可以用于实现关心调用范围的语言功能,例如异常处理或上下文相关的权限处理。在存在延续的情况下,可以进入和退出范围,而无需遍历调用堆栈。

于 2008-11-26T15:15:33.043 回答
7

动态范围允许定义上下文功能。从这个意义上说,它与现代框架中的依赖注入非常相似。(例如,考虑当您使用依赖注入定义注释 Java 类以允许对各种引用进行透明初始化时。(参见 spring 或 JPA 等))

显然,动态作用域对给定函数的调用站点的运行时特性做出了某些假设,这些假设在编译(或设计)时无法保证。再次,按照现代(Java)框架组件的示例,如果您在容器的受控(运行时)环境之外实例化这样的类,则很可能由于其所需的依赖关系,该类将无法运行不会被初始化(又名注入)。

但同样明显的是,组件系统(仅作为一个例子)显然受益于动态绑定机制。依赖注入是实现这一点的框架级手段。动态范围是相同的语言级别的手段。

于 2009-05-03T01:04:03.983 回答
5

另请注意,结合

  • 词法作用域的概念(我们觉得这对编程语言来说是一件好事,而不是动态作用域
  • 函数定义(lambda 表达式)深深嵌入代码中(也可以简称为“嵌套函数”)

从语言实现的角度和程序员的角度来看,这可能是一项复杂的工作。这个复杂的东西甚至还有一个特殊的名字:闭包

正如维基百科所写

在具有一流嵌套函数 的语言中正确实现静态作用域并非易事,因为它要求每个函数值都带有它所依赖的变量值的记录(函数和这个环境的对被称为一个闭包)。

这不仅在具有全局和/或可变变量(如 C 或 Java)的语言中实现并非微不足道;考虑在评估对在该位置范围内的可变状态的闭包时确保正确访问嵌套函数定义!只有一件事:当你在未来某个时间评估闭包时,使用的对象不应该被破坏和垃圾收集),但从概念上来说,程序员思考如何关闭将在复杂的情况下工作以及它将确切具有哪些(副作用)效果(出于相同的原因:您需要考虑闭包与定义闭包时范围内的所有可变状态的交互,例如:当您在闭包内部引用时定义到范围内的外部可变变量,您是否真的想访问变量在定义闭包时所具有的值,即您想要拥有该变量的只读副本,或者您想要在未来评估闭包时全面访问变量的可变状态?)。

在纯函数式语言中,考虑嵌套函数定义及其用途要简单得多,因此只有词法范围对它们来说根本不是问题。但是,如果您的语言不能正常工作,那么它就不是那么微不足道了。(我相信这是长期以来一直争论如何向 Java 添加闭包的原因之一:尽管它们只是建立在词法作用域的好概念之上,但它们似乎并不容易让程序员理解.)

使用动态范围考虑非纯函数式语言中的嵌套函数更简单(尽管动态范围并不好:您获得的编译时检查更少,并保证程序在动态范围内的正确行为)。

所以我认为,考虑到动态范围的所有危险,如果一个人想要并且敢于这样做,那么在一种语言中使用动态范围的优势也可以是用一种简单的方式编程一些东西的可能性。

笔记

关于 Java 中(无)闭包的悠久历史(并且程序员不喜欢这个概念) ——http: //people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg04030.html

日期:2003 年 8 月 14 日星期四 08:05:44 -0700

来自:迈克尔·瓦尼尔

主题:回复:绑定和分配(原:回复:延续)

日期:2003 年 8 月 14 日星期四 10:45:34 -0400

来自:“大卫·B·塔克”

我想,虽然我没有统计证据,但为了在匿名内部类(闭包)中引用它们而将局部变量声明为 final 的要求几乎完全未知并且在实践中未使用。

出于好奇,有谁知道为什么 Java 只允许在匿名类中引用最终变量?

戴夫

   <cynic>Otherwise you'd have the equivalent of true closures,
and if you had that    java would be a
*really* powerful and useful language, so they obviously    couldn't do that.
</cynic>

实际上,原型实现 确实允许从内部类中引用非最终变量。用户强烈抗议,抱怨他们不想要这个!原因很有趣:为了支持这些变量,有必要对它们进行堆分配,而且(至少在那个时候)普通的 Java 程序员仍然对堆分配和垃圾收集以及所有这些东西很敏感。当看不到“new”关键字时,他们不赞成“在桌子下”执行堆分配的语言。

因此,在早期——显然——在 Java 中采用了“第三种”方法(与我在上面提到的两种方法相反):既不是“只读副本”,也不是在评估封闭(在定义闭包时)可变状态的时间,而是状态的可变副本(至少,我这样理解引用的段落;或者不,他在谈论堆分配只是参考?..然后是第二个选项。很好。第三个选项对我来说真的不明智。)。不知道他们现在如何在 Java 中实现闭包,我没有关注最近关于 Java 的新内容。

于 2011-04-02T15:50:24.483 回答
3

动态范围打破了参照透明性,这意味着您无法再对程序进行推理。DS 基本上是类固醇上的全局变量。

于 2010-11-10T10:24:19.597 回答
2

Richard Stallman 的这篇经典文章(来自 GNU/Linux、Emacs、FSF)解释了为什么动态作用域对 Emacs 编辑器和 Emacs Lisp 语言很重要。总之,它对定制很有用。

http://www.gnu.org/software/emacs/emacs-paper.html#SEC17

另请参阅Emacs wiki 上的此页面,了解有关在 Emacs Lisp 中使用动态范围的更多信息:

于 2013-08-23T17:18:08.083 回答
2

我认为 Common LISP 中的动态范围类似于 C 中的全局变量。在功能函数中使用它们是有问题的。

于 2013-10-10T06:09:02.670 回答
1

动态范围的变量是一个强大的,但有时也是不直观和危险的工具。

想象一下,您想要拥有特定于线程的全局变量,即每个线程都有自己的一组全局变量。这可以通过动态范围轻松完成。只需在线程初始化时更改对这个变量的引用。

或者考虑一下异常:它们在大多数语言中都是动态范围的。如果您必须从头开始构建异常系统,您可以使用动态范围的变量轻松地做到这一点。

于 2009-05-03T01:54:26.670 回答
0

Emacs 绑定方式对我来说很方便的一个例子 - 顺便说一句,不确定词法或动态是否是正确的术语。

let 内绑定的变量向下看,不需要显式切换作为参数,这节省了很多击键。

(defun foo1 ()
  (message "%s" a))

(defun foo2 ()
  (let ((a 2))
  (message "%s" a)))

(defun foo3 ()
  (let ((a 1))
    (foo1)
    (foo2)))

==>
1
2

foo2 内部的绑定很有趣,因为这里可能会安装默认值的用法

(let ((a (if (eq something a) 否则分配...

于 2013-08-24T06:17:04.530 回答