Clojure 为什么TCO需要VM的支持?
有些虚拟机,尤其是JVM,据说不支持TCO。因此,像Clojure这样的语言要求用户使用Clojure 为什么TCO需要VM的支持?,clojure,lisp,tail-recursion,tail-call-optimization,Clojure,Lisp,Tail Recursion,Tail Call Optimization,有些虚拟机,尤其是JVM,据说不支持TCO。因此,像Clojure这样的语言要求用户使用循环重现 但是,我可以重写self-tail调用以使用循环。例如,这里有一个尾部调用阶乘: def factorial(x, accum): if x == 1: return accum else: return factorial(x - 1, accum * x) 这里有一个循环等价物: def factorial(x, accum): whil
循环
重现
但是,我可以重写self-tail调用以使用循环。例如,这里有一个尾部调用阶乘:
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而不需要任何VM,为什么语言(例如Clojure、Armed Bear Common Lisp)不这样做呢?我错过了什么
n*fact(n-1)
,这不是尾部递归内联不是一般尾部调用消除问题的解决方案,原因有很多。下面的列表并非详尽无遗。然而,它是被分类的——它以一个不便开始,以一个完整的停止结束
f
中有多个对g
的尾部调用。根据内联的常规定义,您必须在每个调用站点内联g
,这可能会使f
变得巨大。相反,如果您选择转到g
的开头,然后跳回,那么您需要记住跳转到哪里,并且突然之间您正在维护自己的调用堆栈片段(与“真正的”调用堆栈相比,这几乎肯定会表现出较差的性能)
f
和g
,必须在g
中内联f
,在f
中内联g
。显然,根据内联的通常定义,这是不可能的。因此,您只剩下一个有效的自定义函数调用约定(如上面2.中基于goto
的方法)(defn say-foo-then-call [f]
(println "Foo!")
(f))
这可以在某些场景中发挥巨大作用,显然不能用内联来模拟尾部调用消除可以在没有VM支持的情况下使用非本地返回和分派进行模拟。也就是说,当尾部调用在语法上发生时,它被转换为非本地返回,该返回通过动态控制传递放弃当前函数,将参数(可能打包为对象)传递给隐藏的分派循环,该分派循环将控制传递给目标函数,并将这些参数传递给它。这将实现递归在恒定空间中发生的要求,并将“外观和感觉”类似于尾部调用。但是,它很慢,而且可能不是完全透明的。请注意,TCO并不是专门针对自递归调用的。这只是一个特例,没错。但是要么调用同一个函数(可以作为循环编写),要么调用另一个函数(可以内联)。我相信这两种技术结合在一起是一个通用的解决方案。内联不是解决方案。可以简单地在Clojure中内联函数调用吗?我看到的例子是将一个(希望是相应的)宏作为元数据附加到
defn
。您所说的是尾部递归的最简单形式。现在考虑尾部调用虚拟方法。您不一定知道要调用哪个函数。哪些情况不包括重写为循环和内联?你能举个例子吗?嗯,你通常不希望所有的函数调用都内联。答案很好,我没有考虑过很多问题。谢谢:)