应用@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))
}
答案 0 :(得分:5)
试着想象一下这里发生了什么,以及你要求编译器做什么。尾调用优化,粗略地,将方法调用转换为循环,获取方法的参数并将它们转换为在循环的每次迭代中重新分配的变量。
这里有两个这样的“循环变量”:n
和调用get
方法的列表单元格本身,在方法体中实际上是this
。 n
的下一个值很好:它是n - 1
,还有Int
。列表单元格的下一个值tail
是个问题,但是:this
的类型为Cons[T]
,但tail
只有List[T]
类型。
因此,编译器无法将其转换为循环,因为无法保证tail
是Cons[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)
}
}
}
(如果Cons
和Nil
都是案例类,则有效 - 但您可能希望Nil
case object
和List[T]
协变{ {1}}。)
答案 1 :(得分:2)
在Cons.get
中,您拨打tail.get
作为尾部通话。但tail
的类型为List[T]
,而非Cons[T]
。因此调用不一定由Cons.get
处理,并且Scala不能应用尾递归优化;优化会将方法调用转换为本地跳转回Cons.get
的开头,但这不一定是调用的去向。
答案 2 :(得分:2)
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")
}