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

时间:2012-10-15 20:15:19

标签: scala

应用@tailrec我从scala编译器得到错误:“无法优化@tailrec带注释的方法get:它包含一个递归调用,目标是一个超类型case _ => tail.get(n-1)”。有人可以解释为什么会这样吗?

trait List[T] {
  def isEmpty: Boolean
  def head: T
  def tail: List[T]
  def get(n: Int): T
}

class Cons[T](val head: T, val tail: List[T]) extends List[T]{
  def isEmpty = false
  @tailrec
  final def get(n: Int) =
    n match {
      case 0 => head
      case _ => tail.get(n-1)
    }
}

class Nil[T] extends List[T]{
  def isEmpty = true
  def head = throw new NoSuchElementException("Nil.head")
  def tail = throw new NoSuchElementException("Nil.tail")
  final def get(n: Int): T = throw new IndexOutOfBoundsException
}

object Main extends App{
  println(new Cons(4, new Cons(7, new Cons(13, new Nil))).get(3))
}

3 个答案:

答案 0 :(得分:5)

试着想象一下这里发生了什么,以及你要求编译器做什么。尾调用优化,粗略地,将方法调用转换为循环,获取方法的参数并将它们转换为在循环的每次迭代中重新分配的变量。

这里有两个这样的“循环变量”:n和调用get方法的列表单元格本身,在方法体中实际上是thisn的下一个值很好:它是n - 1,还有Int。列表单元格的下一个值tail是个问题,但是:this的类型为Cons[T],但tail只有List[T]类型。

因此,编译器无法将其转换为循环,因为无法保证tailCons[T] - 当然,在列表的末尾,它是Nil

“修复”它的一种方法是:

case class Cons[T](val head: T, val tail: List[T]) extends List[T] {
  def isEmpty = false
  @tailrec
  final def get(n: Int) =
    n match {
      case 0 => head
      case _ => tail match {
        case c @ Cons(_, _) => c.get(n - 1)
        case nil @ Nil() => nil.get(n - 1)
      }
    }
}

(如果ConsNil都是案例类,则有效 - 但您可能希望Nil case objectList[T]协变{ {1}}。)

答案 1 :(得分:2)

Cons.get中,您拨打tail.get作为尾部通话。但tail的类型为List[T],而非Cons[T]。因此调用不一定由Cons.get处理,并且Scala不能应用尾递归优化;优化会将方法调用转换为本地跳转回Cons.get的开头,但这不一定是调用的去向。

答案 2 :(得分:2)

Ben和Jean-Phillipe Pellet已经解释了编译器抱怨的原因。至于如何修复它,有一个简单的解决方案:将get的实现移到List

trait List[T] {
  def isEmpty: Boolean
  def head: T
  def tail: List[T]
  @tailrec
  final def get(n: Int): T = {
    n match {
      case 0 => head
      case _ => tail.get(n-1)
    }
  }
}

class Cons[T](val head: T, val tail: List[T]) extends List[T]{
  def isEmpty = false
}

class Nil[T] extends List[T]{
  def isEmpty = true
  def head = throw new NoSuchElementException("Nil.head")
  def tail = throw new NoSuchElementException("Nil.tail")
}