由debug.getinfo(1,“ n”)。name引起的奇怪行为

时间:2019-07-09 09:52:05

标签: lua

我学习了如何使用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

3 个答案:

答案 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

这是我们感兴趣的两个功能:fooboo

如您所见,当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语言的一部分,与forwhile的行为一样。

如果用户想通过尾部调用来调用您的函数,那么是他们的权利作为使尾部调用成为事物的语言的用户。拒绝使用某一种语言的用户使用该语言的功能的权利是粗鲁

所以不要这样做。

此外,您的代码还建议您尝试依赖具有名称的函数。您正在使用这些名称来做有意义的事情。

好吧,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语句中。显然,这需要常规代码中不必进行的其他内存分配。

所以还有另一个不这样做的原因。