据说某些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

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

因此,假设您无需任何虚拟机就可以实现TCO,那么为什么语言(例如Clojure,Armed Bear Common Lisp)不这样做呢?我错过了什么?

最佳答案

出于多种原因,内联不能解决一般的尾部调用消除问题。以下列表并非详尽无遗。但是,它是经过排序的-它以不便开始,并以完整的topstopper结尾。

  • 在尾部位置调用的函数可能很大,在这种情况下,从性能角度来看,内联它可能不建议使用。
  • 假设g中有多个对f的尾部调用。根据内联的常规定义,您必须在每个调用站点上内联g,这可能会使f变得很大。相反,如果您选择gotog的开头然后跳回,则您需要记住要跳转到的位置,突然之间您要维护自己的调用堆栈片段(与之相比,几乎肯定会表现出较差的性能)。 “真实”调用堆栈)。
  • 对于相互递归的函数fg,您必须内联f中的gg中的f。显然,在通常的内联定义下这是不可能的。因此,剩下的是有效的自定义函数内调用约定(如上述2中基于goto的方法一样)。
  • Real TCE可以在尾部位置使用任意调用,包括在高阶上下文中:
    (defn say-foo-then-call [f]
      (println "Foo!")
      (f))
    

    在某些情况下可以使用此方法产生很大的效果,并且显然不能用内联来模拟。
  • 关于clojure - 为什么TCO需要VM的支持?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23175459/

    10-10 00:55