为什么输入参数在方法中是逆变的?

时间:2015-05-21 06:13:17

标签: scala covariance contravariance

以下是this教程中的一些代码:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend(elem: T): ListNode[T] =
    ListNode(elem, this)
}

教程说:

  

不幸的是,这个程序没有编译,因为协方差   只有在仅使用类型变量时才可以使用注释   协变立场。由于类型变量T显示为参数类型   方法前置,这个规则被打破了。

Tpredend如何处于协变位置,而其他T引用(def head: T = hdef tail: ListNode[T] = t)显然是协变的?

我问的是为什么T中的prepend不是协变的。这肯定不在Why is Function[-A1,...,+B] not about allowing any supertypes as parameters?中,这似乎是其他人指导我阅读的内容。

2 个答案:

答案 0 :(得分:5)

方法的输入参数不是协变位置而是逆变位置。只有返回类型的方法处于协变位置。

如果您对ListNode的定义是确定,那么我可以编写如下代码:

val list1: ListNode[String] = ListNode("abc", null)
val list2: ListNode[Any] = list1  // list2 and list1 are the same thing
val list3: ListNode[Int] = list2.prepend(1) // def prepend(elem: T): ListNode[T]
val x: Int = list3.tail.head // ERROR, x in fact is "abc"

请参阅,如果参数协变位置,那么容器总是可以保存另一种类型的值,该类型具有与其真实类型相同的祖先,这肯定是错误

因此,参考scala.collection.immutable.List.::的源代码,您的类应该定义为:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend[A >: T](elem: A): ListNode[A] = ListNode(elem, this)
}

参数类型A是一个新的类型参数,其下限为T

答案 1 :(得分:1)

一个例子:

val dogList = ListNode[Dog](...)
val animal = Animal()
val dog = Dog()

dogList.prepend(dog) //works
dogList.prepend(animal) //fails

协方差意味着您可以像ListNode [Animal]一样使用ListNode [Dog]

但:

//if compiler would allow it
val animalList: ListNode[Animal] = dogList
animalList.prepend(dog) //works
animalList.prepend(animal) //would fail, but you expect an animallist to accept any animal