连接的结合律

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

另请参阅


RecentChanges · preferences
编辑 · 历史
最后编辑于 2008 年 2 月 2 日下午 1:21 GMT (差异)