Scala:如何更惯用地编写尾递归函数

时间:2015-10-13 03:33:59

标签: scala

我有以下内容:

case class Node(parent: Option[Node], etc:Any)

@tailrec
def getAncestor(numGens:Int, node:Node): Option[Node] =
  if(numGens <= 0) Some(node)
  else node.parent match {
    case Some(parent) => getAncestor(numGens-1, parent)
    case None         => None
  }

我不喜欢case None => None。它给我的印象是应该有一个更优雅的方式。但是,如果我将else替换为:

else node.parent.flatMap(p => getAncestor(numGens-1, p))

然后该函数不再是尾递归的。是否有更惯用的方式来编写这个函数?

3 个答案:

答案 0 :(得分:2)

我认为没有办法。你的方法已经足够简洁了。以下是另外两个选项:

// 1. changing argument type
@tailrec
def getAncestor(numGens:Int, node: Option[Node]): Option[Node] =
  if(numGens <= 0) node else getAncestor(numGens -1, node.flatMap(_.parent))

// 2. removing if
@tailrec
def getAncestor(numGens:Int, node:Node): Option[Node] =
  node.parent match {
    case _ if numGens <= 0 => Some(node)
    case Some(parent) => getAncestor(numGens-1, parent)
    case _ => None
  }

答案 1 :(得分:0)

@tailrec
def getAncestor(numGens:Int, node:Node): Option[Node] = {
  node.parent match {
    case Some(parent) if numGens > 0           => getAncestor(numGens - 1, parent)
    case Some(_) | None if numGens == 0        => Some(node)
    case _                                     => None
  }
}

答案 2 :(得分:0)

此案例类似乎不太有用。由于每个节点只有一个父节点,因此您所做的就是在没有任何有用方法的情况下重新创建List[Any]

如果您将etc个对象放入列表中,则getAncestor(i, n)只会变为

ns drop i

结果将是一个较短的列表,顶部有一个所需的节点或一个空列表。因为Scala的不可变列表是persistent data structure,所以不会浪费任何内存。您可以使用

检索新头节点的内容
ns.drop(i).headOption 

没有堆栈溢出,没有因为尝试使递归函数尾递归而造成的丑陋(有用的显式递归函数在尾递归时几乎总是难看)。更加惯用的Scala,因为它正在使用正确的类型。

如果 尝试使用您的节点构建一些更复杂的结构,那么您最好使用扩展密封特征(或密封的抽象基础)的案例类来构建正确的代数数据类型类)。但是,在扩展这个想法之前,需要有关你的意图的更多信息。