Scala中递归深度的限制

Scala中递归深度的限制,scala,recursion,tail-recursion,Scala,Recursion,Tail Recursion,您是否总能构造一个递归函数来消除尾部调用?如果没有,还有哪些策略限制堆栈大小 例如: (灵感来自) 我们的目标不是挑剔这个特定的函数,而是将它用作学习限制堆栈大小的技术的道具 更新 我的收获是: 如果问题域的递归可能会达到堆栈大小的限制: 将代码重写为tailcall optimizable的scala编译器版本。这可以通过新的(2.8)@scala.annotation.tailrec注释进行辅助/验证 如果不可行,重写它以使用迭代循环构造 我还感觉到这种重写(无论哪种情况)都需要一定的技能

您是否总能构造一个递归函数来消除尾部调用?如果没有,还有哪些策略限制堆栈大小

例如: (灵感来自)

我们的目标不是挑剔这个特定的函数,而是将它用作学习限制堆栈大小的技术的道具


更新

我的收获是:

如果问题域的递归可能会达到堆栈大小的限制:

将代码重写为tailcall optimizable的scala编译器版本。这可以通过新的(2.8)@scala.annotation.tailrec注释进行辅助/验证

如果不可行,重写它以使用迭代循环构造


我还感觉到这种重写(无论哪种情况)都需要一定的技能/才能/智慧/实践。

我认为,所有递归函数都不能表示为尾部递归


然而,你可以将所有递归函数表示为迭代过程。

这里有两种情况需要考虑。在一般情况下,是否有一些递归函数不能表示为尾部调用?[更新]正如另一个答案所指出的,没有

但是,在scala的特定情况下,有些尾部递归函数无法优化为以尾部递归方式运行(这意味着它们重用堆栈帧)。我认为这主要是由于JVM的限制


有关如何工作的更多详细信息,请参见上文。

所有递归函数都可以表示为迭代过程,所有迭代过程都可以表示为尾部递归,因此,严格来说,您可以将任何递归算法转换为尾部递归算法。但是,不要认为这实际上会节省空间。在许多情况下,堆栈所做的记录保持是必要的,最终需要在迭代或尾部递归版本中模拟堆栈。这仍然很有用,比如说当你有一个小堆栈但有一个大堆栈时。

你应该接受答案,但是,对于代码:

// Depth-first search of labyrinth, with large depth > stacklimit
def search ( labyrinth: SearchDomain,
             path: List[SolutionNode],
             goal: DesiredResult ) = {
  if ( path.head == goal ) return path

  candidates: List[SolutionNode] = labyrinth.childNodes(path)

  candidates.find { c =>
    Nil != search( labyrinth, c :: path, goal ) // potential boom!
  } match {
    case Some(c) => c :: path
    case None => Nil
  }
}
变成

// Depth-first search of labyrinth
def search ( labyrinth: SearchDomain,
             path: List[SolutionNode],
             goal: DesiredResult ) = {
  def recurse( candidates: List[List[SolutionNode]],
               path: List[SolutionNode] ) = candidates match {
    case List(Nil) => Nil
    case Nil :: tail => recurse(tail, path.tail)
    case (nextCandidate :: otherCandidates) :: tail => 
      if (nextCandidate == goal)
        nextCandidate :: path
      else
        recurse(labyrinth.childNodes(nextCandidate :: path) :: otherCandidates,
                nextCandidate :: path)
  }

  if ( path.head == goal ) 
    path
  else
    recurse(labyrinth.childNodes(path), path)
}

需要澄清的是,Daniel-您是在指出OP应该如何重新编写其方法以使其具有尾部递归性,还是建议
scalac
编译器将为他执行此优化?Scala编译器不能执行此类优化,因为结果可能并不总是具有与原始结果相同的语义(由于副作用)。在这种情况下,它可以正常工作,但不一定在一般情况下正常工作。为了清楚起见,这是一个如何将其重写为尾部递归的示例。请注意,递归堆栈在通过每个递归调用传递的候选变量中变得显式。另一方面,由于函数是局部的(实际上是闭包),我将迷宫和目标视为“全球”,并避免在堆栈上重复它们。重写递归只需要练习。起初这很难,但随着你习惯了这项技术,它会变得越来越直截了当,如果不一定很容易的话。有时,这不值得付出努力——另一件事你会在练习中注意到。
// Depth-first search of labyrinth
def search ( labyrinth: SearchDomain,
             path: List[SolutionNode],
             goal: DesiredResult ) = {
  def recurse( candidates: List[List[SolutionNode]],
               path: List[SolutionNode] ) = candidates match {
    case List(Nil) => Nil
    case Nil :: tail => recurse(tail, path.tail)
    case (nextCandidate :: otherCandidates) :: tail => 
      if (nextCandidate == goal)
        nextCandidate :: path
      else
        recurse(labyrinth.childNodes(nextCandidate :: path) :: otherCandidates,
                nextCandidate :: path)
  }

  if ( path.head == goal ) 
    path
  else
    recurse(labyrinth.childNodes(path), path)
}