连接的结合律 |
|
.. (连接) 和 ^ (幂)。^ 是右结合的对于大多数语言来说并不奇怪,但 .. 是右结合的则有些不寻常。在大多数情况下,无论 .. 是按右结合还是左结合求值,都不会有影响,因为字符串连接是结合的。但是,如果被连接的对象有元方法,或者有大量的对象被连接(例如 199 个或更多——具体限制由 luaconf.h 中的 LUAI_MAXCCALLS 决定),那么这种影响就会显现出来。.. 之所以是右结合而不是 Lua 运算符通常的左结合,主要原因似乎是为了实现效率(请参阅 LuaList:2002-08/msg00218.html 和 LuaList: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
对象被压入堆栈并“[即时]”进行操作。
__concat 元方法时才可见。如果你的 .. 实现确实是一个连接操作,那么它也将是结合的,但链式连接会被分解为对 __concat 元方法的成对调用,因此你的实现将从右到左被调用。连接不是结合的并且自然右结合的一个例子是 Posix 正则表达式连接;第一个最长匹配规则假设匹配组件是从右到左连接的。我不知道这是否是一个答案:真正的原因可能是它在堆栈机中实现起来更有效率。--RiciLake
^ 进行叉乘,但结合律不同.. 来应用函数装饰器,因为 .. 是右结合的。