【问题标题】:Mutually recursive functions in functional programming languages函数式编程语言中的相互递归函数
【发布时间】:2015-04-30 23:02:06
【问题描述】:

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

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

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

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

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

另一方面,Haskell 不需要这样的语法。是因为 Haskell 的懒惰评估吗?或者正如@augustss 所建议的那样,Haskell 编译器是否也在做与上述相同的事情?

【问题讨论】:

  • 语法对生成什么代码没有影响,除非编译器非常幼稚。
  • F# 在某些情况下确实优化了相互递归函数

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


【解决方案1】:

函数式语言通常会进行所谓的适当的尾调用优化,它完全独立于递归。 任何尾调用都被优化为跳转,无论是递归调用、对先前定义的函数的调用、部分应用的函数,甚至是对一等函数的调用。例如:

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 有一条尾调用指令(尽管已知它很慢)。

【讨论】:

    【解决方案2】:

    由于 F# 属于 ML 家族,我想这是一件相当简单的事情:plain let 根本不是递归的,相互递归的函数需要通过 let rec 绑定在一起。这确实在一定程度上简化了编译器的分析。在 Haskell 中,编译器最终将代码本身分解为相似的块,以支持类型推断和执行优化。可以说,ML 方式更具可预测性。我不认为这两种方法本质上都更好。

    您提到了懒惰的评估,我怀疑这确实有助于在一定程度上平衡每种语言。在 ML 中,递归定义的值几乎必须是一个函数,而在 Haskell 中,任何类型的值都可以递归定义。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-06-13
    • 2022-12-09
    • 2014-08-12
    • 1970-01-01
    • 2015-04-10
    • 2011-09-04
    • 2010-10-25
    相关资源
    最近更新 更多