Lua运算符,为什么没有定义+=,-=等操作符?

71

这是我有一段时间以来一直感到轻微恼怒的问题,但却从未找到答案。

不过,我想至少问一下这个问题,也许有人可以解释一下。

基本上,我曾经使用的许多语言都使用语法糖来编写代码(使用C ++的语法):

int main() {
    int a = 2;
    a += 3; // a=a+3
}

在Lua中,+= 没有定义,所以我需要写成 a=a+3,这只是语法糖的问题。当使用更具有“意义”的变量名,比如 bleed_damage_over_time 这样的变量时,编写代码会变得很繁琐:

bleed_damage_over_time = bleed_damage_over_time + added_bleed_damage_over_time 

替代:

bleed_damage_over_time += added_bleed_damage_over_time

所以,如果你没有一个好的解决方案,我想了解的不是如何解决这个问题,那么在这种情况下,当然我很想听听它;而是为什么lua没有实现这种语法糖。


8
至少有一个第三方补丁实现了这个功能:http://lua-users.org/wiki/LuaPowerPatches(滚动到“组合赋值运算符(5.2)”) - finnw
9
C语言为什么不支持类?在C语言中,为什么不能使用“int a = b = c = 0;”这样的语法?为什么C++中没有像“int a,float b,char *c = 1,2.22,"3rd string";”这样的内存分配方法?请注意,每种语言都有其局限性和优点。 - hjpotter92
1
@hjpotter92,完全没有冒犯,你回答了我提出的问题,证明了一个观点,这正是我想要的。-> +1 - qrikko
顺便提一下,如果你正在寻找一些不错的Lua语法扩展,可以看看MoonScript(https://github.com/leafo/moonscript),它是一个元编程库,通过这个库,你可以扩展Lua语法,生成Lua代码。 - Oliver
1
顺便提一下,像那样的长变量名表明您没有使用适当的数据结构和/或没有将函数限定在合理的范围内。变量名应该在其上下文中具有描述性。 - Jon Ericson
3个回答

94

我只是猜测:

1. 在单次编译器中实现这个很难

Lua的字节码编译器是作为单次递归下降解析器实现的,它立即生成代码。 它不会将解析到一个单独的AST结构,然后在第二遍通过把AST转换成字节码。

这迫使某些语法和语义上存在一些限制。 特别地,任何需要任意前瞻或向前引用的内容在这种模式下都非常难以支持。 这意味着赋值已经很难解析了。 给定类似以下的东西:

foo.bar.baz = "value"

当你解析 foo.bar.baz 时,直到你遇到等号 = 并为其生成代码后才意识到实际上正在解析一个赋值操作。由于这个原因,Lua编译器在处理赋值操作时有相当一部分复杂性。

支持自我赋值将使此过程变得更加困难。例如:

foo.bar.baz += "value"

需要翻译成:

foo.bar.baz = foo.bar.baz + "value"

但是在编译器遇到=的时候,它已经忘记了foo.bar.baz。这是可能的,但不容易。

2. 它可能与语法不兼容

Lua实际上在语法中没有任何语句或行分隔符号。空格会被忽略,也没有必须使用的分号。你可以这样做:

io.write("one")
io.write("two")
或者:
io.write("one") io.write("two")

而Lua对两者都很适应。保持这样的语法不含歧义是很棘手的。我不确定,但自赋值运算符可能会使这更加困难。

3. 它与多重赋值不太兼容

Lua支持多重赋值,如:

a, b, c = someFnThatReturnsThreeValues()

就算你试图做这件事,我也不清楚它的意义是什么。

a, b, c += someFnThatReturnsThreeValues()

你可以将自赋值操作符限制为单一赋值,但那样你只是增加了一个人们必须要知道的怪异情况。

考虑到所有这些问题,很不清楚自赋值操作符是否足够有用,值得去处理以上的问题。


7
非常感谢您抽出时间来回答。您提出了一些非常有说服力和可能性很高的论点回答了我的问题。我之前认为这只是一个无解的问题,但是在阅读了您的回答后,我更像是理解了,并且考虑了所有的论点,甚至同意 Lua 不提供那些运算符的决定。 - qrikko
不客气!这个东西我现在还很清楚,因为我正在实现一种也有单遍编译器的语言,这迫使我意识到它如何影响其设计。 - munificent
1
有人提到递归下降编译器和解析赋值语句的问题?我很了解,但问题主要在于解析左手边。如果你能可靠地找到赋值运算符并理解左手边,那么操作树来生成'+='或等效的表达式并不那么困难。没有使用树来完成这个任务,那就更难了。 - david.pfx
9
1. 但是已经有这种类型的运算符 :。它用于调用函数,同时将你正在使用的表作为第一个参数自动传递,就像 += 等操作符一样。2. 那没有任何意义。行分隔符与此无关吧?它的语法与普通的赋值操作符 = 相同。3. 对我来说听起来非常清楚。您想要对所有三个进行增量操作。 - jv110
1
很不幸,该源代码文档不够完整。 "原型" 是Lua用来指代函数的编译内部表示形式,在它被封装在闭包中之前。这是一个没有其上值的函数。 - munificent
显示剩余2条评论

17

我认为你可以把这个问题简单重述一下:

为什么<languageX>没有来自<languageZ><featureY>

通常,这是语言设计者基于他们对语言所预期用途和目标制定的权衡决策。

就Lua而言,该语言旨在成为嵌入式脚本语言,因此任何使语言更复杂或潜在地使编译器/运行时甚至稍微变大或变慢的更改都可能违反此目标。

如果实现每一个微小的特性,你最终可能会得到一个“厨房水槽”语言:不信,看看 ADA

而且正如你所说,这只是语法糖。


1
谢谢你的回答,它真正提供了我想要的信息。它很有道理,我喜欢你展示问题的一般形式,这让它更加清晰易懂。 - qrikko

13

Lua没有自赋值运算符的另一个原因是,可以使用元表来重载表访问,从而产生任意副作用。对于自赋值,您需要选择展开。

foo.bar.baz += 2

进入

foo.bar.baz = foo.bar.baz + 2

或者成为

local tmp = foo.bar
tmp.baz = tmp.baz + 2

第一个版本运行了foo__index元方法两次,而第二个版本只运行了一次。在语言中不包括自我赋值,并强制您明确表示可以帮助避免此种歧义。


2
第二个选项更明智,像这样 do local t=foo.bar t.baz=t.baz+2 end - Thomas W
10
在Lua中,: 这个运算符与此类似,但工作原理不同。它只会调用一次 __index,然后将其返回值使用两次。 - jv110
Python也因为__setattr____getattr__而在成员访问上产生副作用,但它确实有x += y - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接