Операторы Lua, почему не определено +=, -= и так далее?

это вопрос, который я был слегка раздражен в течение некоторого времени и просто никогда не удосужился найти ответ.

однако я подумал, что могу, по крайней мере, задать вопрос и, возможно, кто-то может объяснить.

в основном многие языки, в которых я работал, используют синтаксический сахар для записи (используя синтаксис из 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 не реализует этот синтаксический сахар.

3 ответов


это только догадки с моей стороны, но:

1. Трудно реализовать это в компиляторе с одним проходом

компилятор байт-кода Lua реализован как однопроходный рекурсивный парсер спуска, который немедленно генерирует код. Он не анализирует отдельную структуру AST, а затем во втором проходе преобразует ее в байт-код.

это накладывает некоторые ограничения на грамматику и семантику. В частности, все, что требует произвольные lookahead или прямые ссылки действительно трудно поддерживать в этой модели. Это означает, что задания уже трудно разобрать. Дано что-то вроде:

foo.bar.baz = "value"

когда вы анализируете foo.bar.baz, вы не понимаете, что на самом деле разбираете задание, пока не нажмете = после того, как вы уже проанализировали и сгенерировали код для этого. Компилятор Lua имеет хорошую сложность только для обработки назначений из-за этого.

поддержка самостоятельного назначения сделать это еще труднее. Что-то вроде:

foo.bar.baz += "value"

необходимо перевести на:

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

но в тот момент, когда компилятор попадает в =, он уже забыл о foo.bar.baz. Это возможно, но нелегко.

2. Он не может играть хорошо с грамматикой

Lua фактически не имеет никаких операторов или разделителей строк в грамматике. Пробелы игнорируются, а обязательные точки с запятой отсутствуют. Вы можете do:

io.write("one")
io.write("two")

или:

io.write("one") io.write("two")

и Луа одинаково доволен обоими. Держать такую грамматику недвусмысленной сложно. Я не уверен, но операторы самостоятельного назначения мая сделать это сложнее.

3. Он не играет хорошо с несколькими назначениями

Lua поддерживает несколько назначений, например:

a, b, c = someFnThatReturnsThreeValues()

мне даже не ясно, что это будет означать, если вы попытаетесь do:

a, b, c += someFnThatReturnsThreeValues()

вы можете ограничить операторы самостоятельного назначения одним назначением, но тогда вы просто добавили странный угловой случай, о котором люди должны знать.

со всем этим совсем не ясно, что операторы самоназначения достаточно полезны, чтобы иметь дело с вышеуказанными проблемами.


Я думаю, вы могли бы просто переписать этот вопрос как

почему <languageX> есть <featureY> С <languageZ>?

обычно это компромисс, который языковые дизайнеры делают на основе своего видения того, для чего предназначен язык, и их целей.

в случае Lua Язык предназначен для встроенного языка сценариев, поэтому любые изменения, которые делают язык более сложным или потенциально делают компилятор / среду выполнения даже чуть больше или медленнее может идти против этой цели.

Если вы реализуете каждую крошечную функцию, вы можете получить язык "кухонная раковина":ада кому-нибудь?

и, как вы говорите, это просто синтаксический сахар.


еще одна причина, по которой Lua не имеет операторов самостоятельного назначения, заключается в том, что доступ к таблице может быть перегружен метатаблями с произвольными побочными эффектами. Для самостоятельного назначения вам нужно будет выбрать desugar

foo.bar.baz += 2

на

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

или

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

первая версия работает __index метаметод для foo дважды, в то время как второй делает это только один раз. Не включая самостоятельное назначение на языке и принуждение вас быть явными помогает избежать этой двусмысленности.