函数式编程语言中的相互递归函数

时间:2015-02-28 05:39:48

标签: haskell f# functional-programming ml mutual-recursion

单个递归函数可以应用尾递归优化,以防止堆栈溢出,但是相互递归函数呢?

这个answer显示了如何在F#中定义相互递归函数:

let rec F() = 
    G()
and G() =
    F()

是否以这种方式定义,以便生成的本机机器代码或字节码最终只包含一个函数,尾递归优化应用于F和G?这会阻止堆栈溢出吗?

对于相互递归的函数,尾调用的算法如何工作?

另一方面,Haskell不需要这样的语法。是因为Haskell的懒惰评估吗?或者@augustss建议,Haskell编译器也是如上所述吗?

2 个答案:

答案 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中,可以递归地定义任何类型的值。