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

Scala 为什么输入参数在方法中是逆变的?,scala,covariance,contravariance,Scala,Covariance,Contravariance,以下是教程中的一些代码: 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显示为参数类型 对于方法prepend

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

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显示为参数类型 对于方法prepend,此规则已被破坏

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

我想问的是为什么
prepend
中的
T
不是协变的。这当然没有包括在内,这似乎是其他人指示我阅读的内容。

一个例子:

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

dogList.prepend(dog) //works
dogList.prepend(animal) //fails
协方差意味着您可以像ListNode[动物]一样使用ListNode[狗]

但是:


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

如果您对
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"
请看,如果参数是协变位置,那么容器可以始终保存另一个类型的值,该类型的值与其实际类型具有相同的祖先,这肯定是错误的

因此,参考的源代码,您的类应定义为:

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

,如所述。其他两种方法<代码> t>代码>处于相反的位置,它只在返回类型中,而对于<代码>预置< /代码>,它也在参数中(<代码> elm:t</代码>),因此它只能是不变量。考虑下面的例子:<代码>性状果实;苹果类水果;梨类扩展水果。假设
ListNode
是协变的,那么
ListNode[Apple]
也是一个
ListNode[Fruit]
val节点:ListNode[Fruit]=ListNode[Apple](new Apple(),null)
。现在,如果您使用另一个
水果
prepend
节点.prepend(new Pear())
,您显然不会得到(typesafe)
ListNode[Apple]
。关于第一个代码段中的第3行。既然
list2.prepend()
返回一个
ListNode[Any]
,并且
Any
Int
的超类型,那么如何将其成功分配给
list3:ListNode[Int]
?ListNode是协变的,而不是逆变的,因此val
list3:ListNode[Int]=list2.prepend(1)
应该失败,不是吗?你是对的。这个例子是无效的:它试图找到一个没有矛盾的地方,因为正如我们所知,来自标准库的协变不可变
List[+a]
可以很好地工作(只要您正确地获得泛型)。如果你想构建一个好的反例,你必须找到一些真正断裂的东西,而不是真正起作用的东西。
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)
}