【问题标题】:Why does TCO require support from the VM?为什么 TCO 需要 VM 的支持?
【发布时间】:2014-06-04 05:20:49
【问题描述】:

据说某些 VM(尤其是 JVM)不支持 TCO。因此,像 Clojure 这样的语言要求用户改用 loop recur

但是,我可以重写自尾调用以使用循环。例如,这是一个尾调用阶乘:

def factorial(x, accum):
    if x == 1:
        return accum
    else:
        return factorial(x - 1, accum * x)

这是一个等效的循环:

def factorial(x, accum):
    while True:
        if x == 1:
            return accum
        else:
            x = x - 1
            accum = accum * x

这可以由编译器完成(我已经编写了执行此操作的宏)。对于相互递归,您可以简单地内联您正在调用的函数。

既然您可以在不需要任何 VM 的情况下实现 TCO,为什么语言(例如 Clojure、Armed Bear Common Lisp)不这样做呢?我错过了什么?

【问题讨论】:

  • 请注意,TCO 并非专门针对自递归调用。这只是一个特例。
  • 是的。但是要么你调用同一个函数(你可以写成一个循环),要么你正在调用另一个函数(你可以内联)。我相信这两种技术结合在一起是一个通用的解决方案。
  • 内联是没有办法的。
  • 能否简单内联 Clojure 中的函数调用?我见过的案例将一个(希望是对应的)宏作为元数据附加到defn
  • 您说的是尾递归最简单的形式。现在考虑尾调用一个虚方法。你不一定知道你在调用哪个函数。

标签: clojure lisp tail-recursion tail-call-optimization


【解决方案1】:
  1. 这可能会令人惊讶,并且可能会使调试更加困难,因为您看不到调用堆栈。

  2. 它只适用于非常特殊的情况,而不是VM支持TCO时处理的一般情况。

  3. 程序员通常不会以尾递归的方式编写代码,除非语言激励他们这样做。例如。递归阶乘通常写成递归步骤为n * fact(n-1),它不是尾递归的。

【讨论】:

  • 哪些情况没有被重写为循环和内联?你能举个例子吗?
  • 嗯,您通常不会希望所有函数调用都内联。
【解决方案2】:

出于多种原因,内联并不是解决一般尾调用消除问题的方法。下面的列表并不详尽。然而,它是有序的——它从不便开始,以一个完整的阻碍结束。

  1. 在尾部位置调用的函数可能很大,在这种情况下,从性能的角度来看,内联它可能是不可取的。

  2. 假设在f 中有多个对g 的尾调用。根据内联的常规定义,您必须在每个调用站点内联g,这可能会使f 变得巨大。相反,如果您选择gotog 的开头然后跳回来,那么您需要记住跳转到的位置,突然之间您正在维护自己的调用堆栈片段(这几乎肯定会表现出较差的性能与“真实”调用堆栈相比)。

  3. 对于相互递归函数 fg,您必须将 f 内联到 ggf。显然,在通常的内联定义下这是不可能的。因此,您只剩下一个有效的自定义函数内调用约定(如上面 2. 的基于 goto 的方法)。

  4. Real TCE 适用于尾部位置的任意调用,包括在高阶上下文中:

    (defn say-foo-then-call [f]
      (println "Foo!")
      (f))
    

    这在某些场景中可以发挥很大的作用,显然不能用内联来模拟。

【讨论】:

  • 出色的答案,很多我没有考虑过的点。谢谢你:)
【解决方案3】:

TCO 本身不需要虚拟机支持。也就是说,不是针对局部函数。跨越外部函数的尾调用需要 VM 支持。理想情况下,尾递归的完整实现允许单独编译的程序单元中的函数在常量空间中相互递归,而不仅仅是一个父函数的本地函数,或者编译器一次都可以看到的函数。

在不支持尾调用的 VM 中,函数调用被封装,并在退出时分配一个新帧。尾调用需要一个特殊的入口点来绕过它。函数可以参与尾调用和非尾调用,因此它们需要两个入口点。

可以在没有 VM 支持的情况下使用非本地返回和调度来模拟尾调用消除。也就是说,当尾部调用在语法上发生时,它被转换为非本地返回,该返回通过动态控制转移放弃当前函数,将参数(可能打包为对象)传递给隐藏的调度循环,该循环将控制权转移到目标函数,将这些参数传递给它。这将实现递归发生在恒定空间中的要求,并且会像尾调用一样“看起来和感觉”。但是,它很慢,而且可能并不完全透明。

【讨论】:

    猜你喜欢
    • 2018-01-26
    • 1970-01-01
    • 2017-10-19
    • 1970-01-01
    • 2020-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-05
    相关资源
    最近更新 更多