这个尾递归斐波那契函数的定义是尾递归的吗?

时间:2015-06-23 07:11:42

标签: scala f# functional-programming tail-recursion continuation-passing

我已经看到了以下F#定义的连续传递式fibonacci函数,我总是认为它是尾递归的:

let fib k =
  let rec fib' k cont =
    match k with
    | 0 | 1 -> cont 1
    | k -> fib' (k-1) (fun a -> fib' (k-2) (fun b -> cont (a+b)))
  fib' k id

在Scala中尝试使用等效代码时,我已经使用了现有的@tailrec,并且当Scala编译器通知我递归调用不在尾部位置时,它被猝不及防:

  def fib(k: Int): Int = {
    @tailrec def go(k: Int, cont: Int => Int): Int = {
      if (k == 0 || k == 1) cont(1)
      else go(k-1, { a => go(k-2, { b => cont(a+b) })})
    }
    go(k, { x => x })
  }

我相信我的Scala实现等同于F#,所以我想知道为什么函数不是尾递归的?

3 个答案:

答案 0 :(得分:3)

第4行对go的第二次调用不在尾部位置,它包含在匿名函数中。 (它位于该函数的尾部位置 ,但不适用于go本身。)

对于延续传递方式,您需要正确的尾调用,但Scala不幸没有。 (为了在JVM上提供PTC,您需要管理自己的堆栈而不使用JVM调用堆栈,这会破坏与其他语言的互操作性,但是,互操作性是Scala的主要设计目标。)

答案 1 :(得分:2)

JVM对尾部呼叫消除的支持是有限的。

我无法谈论F#实现,但在scala中你已经嵌套了调用,所以它不在尾部位置。考虑它的最简单方法是从堆栈的角度来看:在进行递归调用时,堆栈需要记住其他任何信息吗?

在嵌套的go调用的情况下,显然是,因为内部调用必须在计算之前完成并且返回'并完成外部呼叫。

可以递归地定义Fib,如下所示:

def fib(k:Int) = {
  @tailrec
  def go(k:Int, p:Int, c:Int) : Int = {
    if(k == 0) p
    else { go(k-1, c p+c) }
  }
  go(k,0 1)
}

答案 2 :(得分:1)

不幸的是,JVM还不支持尾调用优化(?)(公平地说,它有时可以优化一些调用)。 Scala通过程序转换实现尾递归优化(每个尾递归函数相当于一个循环)。这通常足以用于简单的递归函数,但相互递归或延续传递样式需要完全优化。

使用CPS或monadic风格等高级功能模式时确实存在问题。为了避免堆叠,您需要使用Trampolines。它可以工作,但这既不像正确的尾调用优化那样方便也不高效。关于这个问题的Edward Kmett's comments是一个很好的阅读。