连接运算符的结合性

lua-users home
wiki

Lua 中唯一右结合的运算符是 ..(连接)和 ^(指数)。^ 作为右结合运算符在语言中并不罕见,但 .. 作为右结合运算符则比较少见。在大多数情况下,.. 是右结合还是左结合并不重要,因为字符串连接是结合性的,但如果被连接的对象具有元方法,或者连接了大量对象(例如 199 个或更多 - 精确的限制由 luaconf.h 中的 LUAI_MAXCCALLS 决定),那么效果就会很明显。.. 采用右结合而不是 Lua 运算符通常的左结合的原因似乎主要与实现效率有关(参见 LuaList:2002-08/msg00218.htmlLuaList:2002-08/msg00104.html)。

下面展示了同时连接超过大约 199 个元素的问题。这个 Lua 程序创建了一个无法编译的 Lua 程序

print([[return 0]] .. ([[ .. 0]]):rep(198))

lua test.lua | luac -p -l -
luac: stdin:1: chunk has too many syntax levels

如果我们将 198 减少到 10,我们可以看到原因

main <stdin:0,0> (14 instructions, 56 bytes at 0x671200)
0+ params, 11 slots, 0 upvalues, 0 locals, 1 constant, 0 functions
        1       [1]     LOADK           0 -1    ; ""
        2       [1]     LOADK           1 -1    ; ""
        3       [1]     LOADK           2 -1    ; ""
        4       [1]     LOADK           3 -1    ; ""
        5       [1]     LOADK           4 -1    ; ""
        6       [1]     LOADK           5 -1    ; ""
        7       [1]     LOADK           6 -1    ; ""
        8       [1]     LOADK           7 -1    ; ""
        9       [1]     LOADK           8 -1    ; ""
        10      [1]     LOADK           9 -1    ; ""
        11      [1]     LOADK           10 -1   ; ""
        12      [1]     CONCAT          0 0 10
        13      [1]     RETURN          0 2
        14      [1]     RETURN          0 1

编译器会将所有常量放在连接之前放在堆栈上。当运算符为 ^ 时也会出现相同的错误,尽管对大约 200 个对象进行指数运算的需求可能非常少见,除非 ^ 被重写为不同的语义(这可能不是一个好主意)。

现在,如果使用左结合运算符(例如加法),那么这种类型的代码就可以正常编译

print([[return a]] .. ([[ + a]]):rep(198))

main <stdin:0,0> (399 instructions, 1596 bytes at 0x671200)
0+ params, 2 slots, 0 upvalues, 0 locals, 1 constant, 0 functions
        1       [1]     GETGLOBAL       0 -1    ; a
        2       [1]     GETGLOBAL       1 -1    ; a
        3       [1]     ADD             0 0 1
        4       [1]     GETGLOBAL       1 -1    ; a
        5       [1]     ADD             0 0 1
        6       [1]     GETGLOBAL       1 -1    ; a
        7       [1]     ADD             0 0 1
...
        390     [1]     GETGLOBAL       1 -1    ; a
        391     [1]     ADD             0 0 1
        392     [1]     GETGLOBAL       1 -1    ; a
        393     [1]     ADD             0 0 1
        394     [1]     GETGLOBAL       1 -1    ; a
        395     [1]     ADD             0 0 1
        396     [1]     GETGLOBAL       1 -1    ; a
        397     [1]     ADD             0 0 1
        398     [1]     RETURN          0 2
        399     [1]     RETURN          0 1

这些对象被推送到堆栈上,并在 "[及时]" 时进行操作。

连接作为一种操作是结合性的,因此它的优先级几乎无关紧要;此外,Lua 将链式关联优化为单个(虚拟)操作。连接的优先级结合性只有在为某个对象实现 __concat 元方法时才会显现出来。如果你的 .. 实现确实是连接操作,那么它也将是结合性的,但链式连接会被分解为对 __concat 元方法的成对调用,因此你的实现将从右到左被调用。连接不是结合性并且自然右结合的一种情况是 Posix 正则表达式连接;最长匹配优先规则假设匹配组件是从右到左连接的。我不知道这是否是一个答案:真正的原因可能是它在堆栈机器中实现起来更有效率。--RiciLake

--DavidManura

另请参阅


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2008 年 2 月 2 日下午 7:21 GMT (差异)