@tailrec错误 - “针对超类型的递归调用”

时间:2016-06-01 10:01:31

标签: scala recursion functional-programming

我有以下问题。

class NonEmpty(elem: Tweet, left: TweetSet, right: TweetSet) extends TweetSet {

  @tailrec final def filterAcc(p: Tweet => Boolean, acc: TweetSet): TweetSet =
    if(p(elem))
      (this remove elem).filterAcc(p, acc incl elem)
    else
      (this remove elem).filterAcc(p, acc)

}

Scala告诉我,它无法优化@tairec,因为我的方法包含一个针对超类型的递归调用。 (NonEmpty的超类是类TweetSet,其中定义了filterAcc方法)。如何处理这样的错误?

2 个答案:

答案 0 :(得分:4)

这是预期的行为。由于@tailrec如何工作而发生错误:实质上,编译器尝试将递归调用转换为本地循环。尽管你的方法是递归的,但是它通过在另一个甚至不是同一类型的实例上调用自己来进行递归,而是超类型 - 因为TweetSet的其他子类可能提供完全不同的{{{}实现。 1}},编译器无法确保此方法可以安全地扩展为循环。

尾递归函数的经验法则是确保它始终将本身完全调用为最后一个语句 - 在您的情况下,而不是调用

filterAcc

您必须尝试以模仿(this remove elem).filterAcc(...) 并调用

的方式转换acc
this remove elem

在涉及递归的函数式编程中,最好在超类中定义整个方法(在您的情况下为filterAcc(p, <transformed acc>) )并依赖于ADT成员的模式匹配而不是多态 - 这样就没有问题优化递归。

更新:有一个很好的blog post解释尾递归,正如this question

的回答中所指定的那样

答案 1 :(得分:0)

在这种情况下,将filterAcc移动到伴侣对象中可能是有意义的:

trait TweetSet {
  final def filterAcc(p: Tweet => Boolean) = TweetSet.filterAcc(p, this, Empty)
}

object TweetSet {
  @tailrec 
  private def filterAcc(p: Tweet => Boolean, self: TweetSet, acc: TweetSet) = self match {
    case ne: NonEmpty =>
      // the original body with `this` replaced by `self` and made an argument for recursive calls
      val elem = ne.elem
      if(p(elem))
        filterAcc(p, self remove elem, acc incl elem)
      else
        filterAcc(p, self remove elem, acc)
    // other cases
  }
}