【问题标题】:Why are input parameters contravariant in methods?为什么输入参数在方法中是逆变的?
【发布时间】:2015-05-21 06:13:16
【问题描述】:

这里有一些来自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 作为参数类型出现 的方法前置,这条规则被打破了。

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? 中肯定没有涉及,这似乎是其他人指导我阅读的内容。

【问题讨论】:

  • 如所写。其他两个方法中T处于逆变位置,返回类型only,而prepend也在参数(elem: T)中,所以只能保持不变。
  • 考虑以下示例:trait Fruit; class Apple extends Fruit; class Pear extends Fruit。假设ListNode 是协变的,所以ListNode[Apple] 也是ListNode[Fruit]:val node: ListNode[Fruit] = ListNode[Apple](new Apple(), null)。现在,如果你 prepend 和另一个 Fruitnode.prepend(new Pear()),你显然不会得到一个(类型安全的)ListNode[Apple]

标签: scala covariance contravariance


【解决方案1】:

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

如果您对ListNode 类的定义 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.::的源码,你的类应该是这样定义的:

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

【讨论】:

  • 关于第一个 sn-p 中的第 3 行。既然list2.prepend() 返回一个ListNode[Any],而AnyInt 的超类型,那么如何将它成功分配给list3: ListNode[Int]? ListNode 是协变的,而不是逆变的,所以 val list3: ListNode[Int] = list2.prepend(1) 应该失败,不是吗?
  • 你是对的。这个例子是无效的:它试图在没有矛盾的地方找到矛盾,因为我们知道,标准库中的协变不可变List[+A] 工作得很好(只要你得到正确的泛型)。如果你想构建一个好的反例,你就必须找到真正破坏的东西,而不是真正有效的东西
【解决方案2】:

一个例子:

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

【讨论】:

    猜你喜欢
    • 2016-09-24
    • 2010-11-23
    • 2012-03-26
    • 1970-01-01
    • 2014-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多