除了 Logo 和 Emacs Lisp 之外,还有哪些纯动态作用域语言?
动态作用域语言有哪些示例?选择这种设计的原因是什么?是因为它很容易实现吗?
What are some examples of a dynamically scoped language? And what are the reasons for choosing that design? Is it because it is easy to implement?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
嗯,有很多网站讨论利弊,所以我不会去那里。
XSLT 是一种有趣的语言,它的一些功能有点类似于动态作用域。虽然 XSLT 的模板和变量等是词法范围的,但 XSLT 当然都是关于 XML 的 - 并且 xml 树中的当前位置是“动态范围的”,因为上下文节点是全局的,因此不评估 XPath 表达式根据 XSLT 的词法范围,但根据其动态评估。
Well, there's a bunch of websites that discuss the pro's and con's, so I'm not going there.
One interesting language that has some features that faintly resemble dynamic scope is XSLT; although XSLT's templates and variables and the like are lexically scoped, XSLT is of course all about XML - and the current position in the xml tree is "dynamically scoped" in the sense that the context node is global and thus that XPath expressions are evaluated not according to XSLT's lexical scope but according to it's dynamic evaluation.
使用解释器更容易实现动态作用域。大多数早期的 Lisp 解释器都使用动态作用域。几年后,人们发现词法作用域具有优势,但最初主要在 Lisp 编译器中实现。出现了几种实现,它们在解释代码中实现动态作用域,在编译代码中实现词法作用域。有些提供了特殊的语言构造来提供闭包。像Scheme和Common Lisp这样的Lisp方言要求解释代码和编译代码之间没有区别,因此基于解释的实现也必须实现词法作用域。
早期的 Smalltalk 实现实现了动态作用域。各种 Lisp 方言实现都实现了动态作用域(Interlisp、UCI Lisp、Lisp Machine Lisp、MacLisp,...)。
过去 20 年几乎所有新的 Lisp 方言都默认甚至专门使用词法作用域。一些出版物已经详细描述了如何使用词法作用域来实现 Lisp - 因此没有理由不使用词法作用域。
Dynamic scope is/was easier to implement with interpreters. Most early Lisp interpreters were using dynamic scope. After several years lexical scope was found to have an advantage, but was first mostly implemented in Lisp compilers. Several implementations appeared that implemented dynamic scope in interpreted code and lexical scope in compiled code. Some provided a special language construct to provide closures. Lisp dialects like Scheme and Common Lisp required then that there is no difference between interpreted and compiled code and thus interpreted based implementations had to implement lexical scope, too.
Early Smalltalk implementations implemented dynamic scope. All kinds of Lisp dialect implementations implemented dynamic scope (Interlisp, UCI Lisp, Lisp Machine Lisp, MacLisp, ...).
Almost all new Lisp dialects from the last 20 years use lexical scope by default or even exclusively. Several publications have described in detail how to implement Lisp with lexical scope - so there is no excuse not to use lexical scope.
m4 有pushdef/popdef,这是典型的动态作用域实现。
m4 has pushdef/popdef which is a typical dynamic scope implementation.
Mathematica 是另一种通过
Block
构造动态限定作用域的语言。这在使用公式时实际上非常有用。它允许您编写这样的内容,如果像
a
和t
这样的变量按词法限定作用域,则根本无法工作。它与 Mathematica 的规则重写系统配合得特别好,如果没有现有的定义,该系统将保留未计算的变量(作为符号表达式)。Mathematica 可以使用
Module
构造来伪造词法作用域,但这实际上是用新的、据称是唯一的符号重写表达式(如果您预测下一个唯一符号是什么,则可能会导致冲突,在大多数情况下这很容易)。这意味着将变成这样的东西:
Emacs Lisp,在它的一个库中,有一个名为
lexical-let
的构造(实际上是一个 Lisp 宏),它使用完全相同的技巧来伪造词法作用域。当您编译语言时,真正的词法作用域具有性能优势,这是 ELisp 或 Mathematica 的假词法所无法获得的,因为您需要动态变量与其当前值之间的某种映射,这意味着进行查找(通过哈希表或属性列表或其他东西)和附加的间接层。
编辑:如果只有词法变量,则可以通过在进入作用域时存储全局词法变量的原始值并保证在退出作用域时恢复旧值来伪造动态作用域。为了确保这一点,您需要像 Lisp 的
UNWIND-PROTECT
或finally
块这样的东西。我也看到过使用 C++ 析构函数完成此操作,主要是作为练习。Mathematica is another language that is dynamically scoped, via the
Block
construct. This is actually quite useful when working with formulas. It allows you to write things likewhich wouldn't work at all if variables like
a
andt
were scoped lexically. It works particularly nicely with Mathematica's rule-rewriting system, which will, among other things, leave variables unevaluated (as symbolic expressions) if it doesn't have an existing definition for them.Mathematica can fake lexical scoping with the
Module
construct, but what this really does is rewrite the expression in terms of new, allegedly unique symbol (you can cause clashes if you predict what the next unique symbol will be, which is easy in most cases). This meanswill be turned into something like this:
Emacs Lisp, in one of its libraries, has a construct (really a Lisp macro) called
lexical-let
that pulls exactly the same trick to fake lexical scoping.There are performance advantages to real lexical scoping when you're compiling your language which you don't get with the fake lexicals of ELisp or Mathematica, since you need some mapping between the dynamic variable and its current value, which means doing lookups (through a hash table or property list or something) and additional layers of indirection.
EDIT: If you have only lexical variables, you can fake dynamic scoping by storing the original value of a global, lexical variable on entering the scope and guaranteeing that the old value is restored upon exiting the scope. In order to ensure that, you'll need something like Lisp's
UNWIND-PROTECT
or afinally
block. I've seen this done using C++ destructors as well, mostly as an exercise.动态作用域语言更容易实现。要访问不在当前激活记录/堆栈帧中的变量,只需遵循控制链接即可。这样就不需要静态/词法访问链接,从而使堆栈帧更小。
动态变量在运行时可能是“不可预测的”,因为需要知道实际堆栈帧的顺序才能知道将使用哪个变量。仅通过查看代码的静态结构无法获得此信息。如果程序的实际调用图在实现时不容易预测,那么人们很容易就会被抓住。这就是为什么当今大多数语言都具有静态作用域(然而,大多数异常系统都是动态的,因为这是最实用的)。
然而在某些情况下,动态范围的变量非常有用。例如,在重定向输出时,您可以使用动态变量为本地代码以及从那里调用的所有代码设置标准输出。
在这个 common-lisp 示例(来自 Seibel)中,标准输出在 let 形式的持续时间内绑定到另一个流(在其封闭的括号内)。当执行离开let时,它会返回到之前的状态。请参阅 http://gigamonkeys.com/book/variables.html Peter Seibels 的免费优秀书籍,实用 Common Lisp,进行了很好的讨论。用塞贝尔自己的话说:
Dynamically scoped languages are much easier to implement. To access variables which is not in the current activaiton record / stack frame, one just follows the control links. Static/lexical access links are then not needed, making stack frames smaller.
Dynamic variables can be "unpredictable" at runtime, because one needs to know in which order the actual stackframes are to know which variable will be used. This information is not available by just looking at the static structure of the code. One could quite easily get caught out if the actual call graph of the program is not easy to predict at implementation time. Thats why most languages today have static scoping (most Exception systems however, are dynamic as this is the most practical).
However in some cases, dynamically scoped variables are very useful. For example when redirecting output, you could using dynamic variables set standard output for local code and all code called from there on.
In this common-lisp example (from Seibel), standard output is bound to another stream for the duration of the let form, (inside its enclosing parens). When execution leaves the let, it goes back to whatever it was beforehand. See http://gigamonkeys.com/book/variables.html Peter Seibels free and excellent book, Practical Common Lisp, for a good discussion. In Seibels own words:
所有 shell 语言(bash、ksh 等)都使用动态作用域。
All the shell languages (bash, ksh, etc) use dynamic scoping.