Вы можете запустить свой код через luac -l -p
...
function <stdin:6,8> (4 instructions at 0x555f561592a0)
0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions
1 [7] GETTABUP 0 0 -1 ; _ENV "myFunc"
2 [7] TAILCALL 0 1 0
3 [7] RETURN 0 0
4 [8] RETURN 0 1
function <stdin:10,13> (4 instructions at 0x555f561593b0)
0 params, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions
1 [11] GETTABUP 0 0 -1 ; _ENV "myFunc"
2 [11] CALL 0 1 2
3 [12] RETURN 0 2
4 [13] RETURN 0 1
Эти две функции, которые нам интересны: foo
и boo
Как видитекогда boo
вызывает myFunc
, это просто обычный CALL
, так что ничего интересного там нет.
foo
, однако, делает то, что называется tail call .Таким образом, возвращаемое значение foo
является возвращаемым значением myFunc
.
. Что делает этот тип вызова особенным, так это то, что программе не нужно переходить обратно в foo
;как только foo
звонит myFunc
, он может просто передать ключи и сказать: «Вы знаете, что делать»;myFunc
затем возвращает свои результаты непосредственно туда, куда был вызван foo
.Это имеет два преимущества:
- Кадр стека
foo
можно очистить до того, как myFunc
будет вызван - , как только
myFunc
вернется, ему не нужно два прыжкавернуться в основной поток;только один
Оба примера незначительны в таких примерах, как ваш, но если у вас есть цепочка лотов и концевых вызовов, это становится значительным.
Недостатком этого являетсячто после очистки стека foo
Lua также забывает всю информацию об отладке, связанную с ним;он только помнит, что myFunc
был вызван как хвостовой вызов, но не от куда.
Интересное примечание, что boo
почти также является хвостовым вызовом.Если у Lua не было нескольких возвращаемых значений, он был бы точно таким же, как foo
, и более умный компилятор, такой как LuaJIT, мог бы скомпилировать его в хвостовой вызов.PUC Lua не хочет, хотя, так как для распознавания хвостового вызова требуется литерал return some_function()
.
Разница в том, что boo
возвращает только первое значение, возвращаемое myFunc
, и в то время как в вашем примере, будет только один, интерпретатор не может сделать такое предположение (LuaJIT может сделать это предположение во время компиляции JIT, но это за пределами моего понимания)
Также обратите внимание, что, технически, слово хвостовой вызов просто описывает функцию A, непосредственно возвращающую возвращаемое значение другой функции B.
Он часто используется взаимозаменяемо с оптимизацией хвостового вызова , что и делает компиляторкогда он повторно использует кадр стека и превращает вызов функции в скачок.
Строго говоря, C (например) имеет хвостовые вызовы , но не имеет хвостового вызоваОптимизация , то есть что-то вроде
int recursive(n) { return recursive(n+1); }
является допустимым кодом C, но в конечном итоге приведет к переполнению стека, тогда как в Lua
local function recursive(n) return recursive(n+1) end
просто запуститсянавсегда.Оба являются хвостовыми вызовами, но оптимизируется только второй.
РЕДАКТИРОВАТЬ: Как всегда с C, некоторые компиляторы могут самостоятельно реализовать оптимизацию хвостовых вызовов, так что не говорите всем, что«С никогда не делает это»;это просто не обязательная часть языка, в то время как в Lua это фактически определено в спецификации языка, так что это не Lua, пока у него нет TCO.