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

Why are input parameters contravariant in methods?

这是 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)
}

教程说:

Unfortunately, this program does not compile, because a covariance annotation is only possible if the type variable is used only in covariant positions. Since type variable T appears as a parameter type of method prepend, this rule is broken.

为什么 T 不在 predend 中的协变位置,而其他 T 引用(def head: T = hdef tail: ListNode[T] = t)显然是协变?

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

一个例子:

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

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

如果你对 ListNode class 的定义是 OK,那么我可以这样写代码:

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.::的源代码,你的class应该是这样定义的:

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