lua 5.2 正式发布了,对于 lua 语言本身的修改,重中之重就是对 environment 这个概念的修改。
可以说, 5.1 以前的 environment 已经没有了。environment 对于制造一个安全的沙盒(或是实现 DSL)是一个很重要的语言特性,我以前很喜欢使用,但也很容易用错。这次的修改我认为是一个谨慎的决定,并使得 lua 语言更为精简和严谨了。
我这样理解 5.2 中的 environment 。本质上,lua 取消了原有意义上的 environment 。所以我们可以看到 C Function 不再有环境了。function 、在 lua 中称为 closure ,仅仅只是函数体和 upvalue 的联合体。这简化了 lua 语言本身。全局变量实际上只是一个语法糖,编译时再前面加上了 _ENV.
的前缀。这样,从 load 开始,第一个 chunk 就被加上了 _ENV
这个 upvalue ,然后依次传递下去。
这个设计基本可以取代以前使用 getfenv/setfenv 改变函数环境的方法。但是又不完全等价。总体来说,增加了一些限制,但不太容易写出 bug 的代码了。
比如说,现在想给返回一个独立环境的函数,可以这样写:
function foobar(env) local _ENV = env return function() ... end end
而以前大约是这样:
function foobar(env) return setfenv(function() ... end, env) end
这不太看得出好坏,但是如果是一组函数,就有区别了。5.2 中是这样:
function foobar(env) local _ENV = env local ret = {} function ret.foo() ... end function ret.bar() ... end return ret end
5.1 的等价代码大约是这样:
function foobar(env) local old = getfenv() setfenv(1,env) local ret = {} function ret.foo() ... end function ret.bar() ... end setfenv(1,old) return ret end
或者这样更函数式一点:
function foobar(env) return setfenv( function() local ret = {} function ret.foo() ... end function ret.bar() ... end return ret end , env) () end
getfenv/setfenv 更灵活,却更容易出错。
对于制作沙盒来说,我感觉 lua 5.2 会更为鼓励使用 load 这种运行时的编译行为。即一定程度上的鼓励元编程。(因为取消了 setfenv ,所以给了 load 显式的参数来制定给 chunk 一个新的环境)
btw, 这个语言设计变更的同时也增强了函数式编程的性能。因为 lua 现在可以更方便的合并那些有相同 upvalue 的 closure 了。(从前除了 upvalue 还有 environment ,合并行为更为复杂)
12 月 30 日补充:
如果非要类似 setfenv 的功能, 修改一组函数的 _ENV
大概需要这样做了:
function getfuncs() local _ENV = _ENV local ret = {} function ret.foo() ... end function ret.setfenv(env) _ENV = env end return ret end