单个递归函数可以应用尾递归优化,以防止堆栈溢出,但是相互递归函数呢?
这个answer显示了如何在F#中定义相互递归函数:
let rec F() =
G()
and G() =
F()
是否以这种方式定义,以便生成的本机机器代码或字节码最终只包含一个函数,尾递归优化应用于F和G?这会阻止堆栈溢出吗?
对于相互递归的函数,尾调用的算法如何工作?
另一方面,Haskell不需要这样的语法。是因为Haskell的懒惰评估吗?或者@augustss建议,Haskell编译器也是如上所述吗?
答案 0 :(得分:5)
函数式语言通常执行所谓的正确的尾部调用优化,它完全独立于递归。 任何尾部调用都被优化为跳转,无论是递归调用,对先前定义的函数的调用,部分应用的函数,还是对第一类函数的调用。例如:
g x = let y = 2*x in abs x -- tail call
add x = (+) x -- tail call
apply f x = f x -- tail call
F#应该能够做到这一切,因为CLR有一个尾调用指令(即使已知它很慢)。
答案 1 :(得分:1)
由于F#属于ML系列,我想这是一个相当简单的问题:普通let
根本不是递归的,并且相互递归函数需要通过{{1}绑定在一起}}。这确实在一定程度上简化了编译器的分析。在Haskell中,编译器最终将代码分解为类似的块本身,既支持类型推断又执行优化。 ML方式可以说更具可预测性。我不认为这两种方法本来就更好。
你提到了懒惰的评价,我怀疑这确实有助于在每种语言中平衡某种程度的平衡。在ML中,递归定义的值几乎必须是函数,而在Haskell中,可以递归地定义任何类型的值。