Lua 作用域讨论 |
|
版本说明:此页面上的评论参考的是一个相当古老的 Lua 版本(2001 年左右),该版本具有更有限的词法作用域,可能还有不完整的闭包支持。其中许多评论不适用于最近版本的 Lua。
Lua 是静态作用域 [1]。
关于这种情况是否应称为有限/损坏的静态/词法作用域,存在一些分歧。一些计算机语言书籍将词法作用域和静态作用域定义为相同,这使得问题更加复杂。当访问的变量处于全局作用域时,Lua 的行为确实是词法作用域。
do
-- assume "a" is a global with value 5
local foo = function() print(a) end
a = 10
foo() -- prints "10"
end
当使用 upvalue 访问下一个外部作用域时,只要变量没有被重新绑定(无论是从变量的作用域还是从嵌套函数),Lua 的行为也像是词法作用域。
do
local a = 5
local foo = function() print(%a) end
foo() -- prints "5"
end
无论如何,Lua 可能不像早期的一些脚本语言(如 Perl)那样糟糕。Perl 最初是动态作用域。它已慢慢演变,通过 my 限定符支持词法作用域。词法作用域可能在 Perl 6 中成为默认设置。
Python 之前也具有类似 Lua 的有限静态作用域。在 2.2 版本中,词法作用域将成为默认设置,并且在 2.1 中已作为一种选项 [2]。Python 的词法作用域实现不允许从嵌套函数内部重新绑定变量,这与 Lua 的 upvalue 类似。
对于 Lua 来说,也许只需要将其有限的子集扩展为真正的词法作用域。它可以延续 upvalue 的概念,并且不允许像 Python 那样重新绑定。或者,它也可以允许重新绑定,从而消除 upvalue 的需求,而 upvalue 常常是 Lua 程序员感到困惑的根源。在 Python 词法作用域的设计文档中,不允许多重绑定的主要原因是 Python 缺乏变量声明。由于 Lua 需要 local 来限定变量,这不会成为一个问题。
-- E. Toernig
摘自 Aho, Sethi 和 Ullman 著《编译原理、技术和工具》(1986 年,Addison-Wesley)第 411 页
将 FOLDOC 条目与“龙书”中的内容进行比较,后者的定义似乎更宽松一些,因为它没有将“最小块”的规定视为必需,而只将其视为某些语言(如 C)的属性。换句话说,只要某个作用域规则不是动态的(即不依赖于当前激活),就可以被认为是静态的。
在 Lua 语言中,每个变量要么是局部作用域,要么是全局作用域。函数可以定义在函数内部,但是,嵌套函数无法访问其任何外层函数中定义的变量。因此,Lua 变量缺乏静态嵌套作用域。结果,以下 Lua 代码是非法的:
function addn(x)
function sum(y)
return x+y
end
return sum
end
print((addn(3))(1))
此示例是非法的,因为在 addn 中定义的变量 x 对嵌套函数 sum 不可访问。
嵌套函数可以访问其直接外层函数中定义的变量的副本。对函数内的变量的 upvalue 引用在函数被求值以生成闭包时提取此副本。以百分号(%)开头的变量引用表示 upvalue 引用。在 sum 中,在 x 引用前加上百分号,可以使上述示例成为合法的 Lua 代码。
这种对静态嵌套作用域的糟糕替代方案被采用,是因为它易于实现,使得所有非全局变量都可以分配在栈上,但是,不必放弃静态嵌套作用域就可以拥有分配在栈上的非全局变量。
2.2 版本之前的 Python 语言具有与 Lua 类似的当前规则——每个变量要么是局部作用域,要么是全局作用域。从 2.2 版本开始,Python 具有静态嵌套作用域。在 Python 的实现中,从嵌套函数中引用的非全局变量是不可变的。有了这个额外的假设,栈分配的非全局变量很容易实现。
Python 的实现证明了这种方法的有效性。正如 Luca Cardelli 在 1984 年的论文《编译函数式语言》[3] 中所述,他们通过使用扁平闭包(flat closures)实现了快速实现。相关部分是关于“获取变量”的第 4 部分。
对于我们这些希望 Lua 未来版本能够拥有静态嵌套作用域变量的人来说,我建议我们可以通过寻找可以被核心 Lua 实现者使用的实现技术来提供帮助。我认为我们应该仔细研究 Python 2.2 实现的相关部分以获取灵感,并使其可用。
我相信用一种优秀的、非常高级的语言编写的程序,应该能让偶尔熟悉该语言的人轻松阅读。除了 upvalue 之外,我认为 Lua 在这方面表现出色,因为它采用了类似 Pascal 的语法来实现其含义正如人们所期望的控制结构。使用 upvalue 的程序不太可能被普通用户理解。人们需要手册才能理解它们何时获得值。大多数人凭直觉就能理解静态嵌套作用域。当变量从嵌套函数内部被引用时,将变量设为不可变会给 Lua 程序的作者带来负担,但不会给程序的读者带来负担。我认为 Lua 的设计应首先满足读者的需求。
以下是静态嵌套作用域的定义
-- John D. Ramsdell