Why are input parameters contravariant in methods?

2020-07-20 04:14发布

问题:

Here's some code from this tutorial:

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

The tutorial says:

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.

How is T not in a covariant position in predend, and the other T references (def head: T = h and def tail: ListNode[T] = t), apparently, are covariant?

What I'm asking is why T in prepend is not covariant. This is certainly not covered in Why is Function[-A1,...,+B] not about allowing any supertypes as parameters?, which seems to be what others have directed me to read.

回答1:

Input parameters of methods are not in covariant positions but contravariant positions. Only the return types of methods are in covariant positions.

If your defination of ListNode class were OK, then I could write code like this:

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"

See, if arguments were covariant positions, then a container could always hold values of another type which has the same ancestor of its real type, and this is definitely WRONG!

So, referring to the source code of scala.collection.immutable.List.::, your class should be defined as this:

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

The argument type A is a new type parameter which is lower bounded to T.



回答2:

An example:

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

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

covariance means that you can use a ListNode[Dog] like a ListNode[Animal]

but:

//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