我学习了如何使用debug.getinfo(1, "n").name
在函数内部获取函数名称。
使用此功能,我发现了Lua中的异常行为。
这是我的代码:
function myFunc()
local name = debug.getinfo(1, "n").name
return name
end
function foo()
return myFunc()
end
function boo()
local name = myFunc()
return name
end
print(foo())
print(boo())
结果:
nil
myFunc
如您所见,函数foo()
和boo()
调用相同的函数myFunc()
,但是它们返回不同的结果。
如果我用其他字符串替换debug.getinfo(1, "n").name
,它们将返回与预期相同的结果,但我不理解由使用debug.getinfo()
引起的意外行为。
是否可以更正myFunc()
函数,以便同时调用foo()
和boo()
函数返回相同的结果?
预期结果:
myFunc
myFunc
答案 0 :(得分:1)
这是Lua所做的tail call optimisation的结果。
在这种情况下,Lua将函数调用转换为“ goto”语句,并且不使用任何额外的堆栈框架执行尾部调用。
您可以添加const ftpClient = new ftp();
ftpClient.on('error',console.dir);//add error listener.
// rest code...
语句来检查它:
SELECT TO_CHAR(TRANSBDATE,'MON-YYYY')PERIOD,B.CURCODE,COUNT(DISTINCT CUSTOMERID) UNIQUECUSTOMERS,COUNT(CURCODE)TRANS
FROM FX_TRANSHEADER A,FX_TRANSDETAIL B
WHERE A.TRANSNO = B.TRANSNO AND A.TRANSBDATE BETWEEN '01-JAN-2018' AND '30-JUN-2019' AND C_IDTYPE NOT IN ('CR')
ORDER BY 1 , 4
GROUP BY TO_CHAR(TRANSBDATE,'MON-YYYY')PERIOD,COUNT(DISTINCT CUSTOMERID),COUNT(CURCODE)
当您通过函数调用返回时,尾调用优化会在Lua中发生:
traceback
您可以参考以下链接以获取更多信息: -Programming in Lua - 6.3: Proper Tail Calls -What is tail call optimisation? - Stack Overflow
答案 1 :(得分:1)
您可以通过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编译期间的那个假设,但这超出了我的理解)
还要注意,从技术上讲, tail call 一词仅描述了一个函数A,该函数直接返回另一个函数B的返回值。
它经常与 tail调用优化互换使用,这是编译器在重用堆栈框架并将函数调用转换为跳转时所做的事情。
严格来说,C(例如)具有 tail调用,但是没有 tail调用优化,意思是
int recursive(n) { return recursive(n+1); }
是有效的C代码,但最终在Lua中会导致堆栈溢出
local function recursive(n) return recursive(n+1) end
将永远运行。两者都是尾调用,但只有第二个得到优化。
编辑:和C一样,某些编译器可能会自己实现尾调用优化,因此不要四处告诉所有人“ C从来没有做过”。它不是语言的必需部分,而在Lua中它实际上是在语言规范中定义的,因此在拥有TCO之前不是Lua。
答案 2 :(得分:1)
在Lua中,任何return <expression_yielding_a_function>(...)
形式的return语句都是“尾部调用”。尾部调用在调用堆栈中根本不存在,因此它们不占用额外的空间或资源。您有效调用的函数将从调试信息中删除。
是否可以更正
myFunc()
函数,以便同时调用foo()
和boo()
函数返回相同的结果?
嗯...是的,但是在我告诉你如何做之前,请允许我说服您不要这样做。
如前所述,尾调用是Lua语言的一部分。从堆栈中删除尾部调用不是“优化”,而是在您使用for
时退出break
循环的“优化”。这是Lua语法的一部分,Lua程序员有权期望尾调用为尾调用,就像他们有权期望break
退出循环一样。
卢阿(Lua),作为一种语言,明确指出:
local function recursive(...)
--some terminating condition
return recursive(modified_args)
end
永远不会,永远,不会耗尽堆栈空间。它与执行循环一样有效地利用堆栈空间。这是Lua语言的一部分,与for
和while
的行为一样。
如果用户想通过尾部调用来调用您的函数,那么是他们的权利作为使尾部调用成为事物的语言的用户。拒绝使用某一种语言的用户使用该语言的功能的权利是粗鲁。
所以不要这样做。
此外,您的代码还建议您尝试依赖具有名称的函数。您正在使用这些名称来做有意义的事情。
好吧,Lua 不是Python ; Lua函数不必具有名称,句点。因此,您不应编写有意义地依赖于函数名称的代码。为了调试或记录目的,很好。但是,您不应仅出于调试和日志记录就打破用户期望。因此,如果用户拨打了电话,只需接受用户想要的,您的调试/日志记录就会受到一点影响。
好的,那么,我们是否同意您不应该这样做? Lua用户有权终止呼叫,而您无权拒绝? Lua函数没有命名,您不应该编写要求它们保持名称的代码吗?好吧
接下来是 您永远不应该使用的可怕代码! (在Lua 5.3中):
function bypass_tail_call(Func)
local function tail_call_bypass(...)
local rets = table.pack(Func(...))
return table.unpack(rets, rets.n)
end
return tail_call_bypass
end
然后,只需将您的实函数替换为旁路的返回即可:
function myFunc()
local name = debug.getinfo(1, "n").name
return name
end
myFunc = bypass_tail_call(myFunc)
请注意,绕过函数必须构建一个数组来保存返回值,然后将它们解包到最终的return语句中。显然,这需要常规代码中不必进行的其他内存分配。
所以还有另一个不这样做的原因。