【问题标题】:Why method defined like "cons[B >: A](v: B)" accepts argument of type which is not supertype of A?为什么定义为“cons [B >:A](v:B)”的方法接受不是A的超类型的类型的参数?
【发布时间】:2016-05-19 21:53:03
【问题描述】:

我现在正在研究 scala 中的方差,我想我对逆变有很好的理解。例如给定trait List[-A],我知道List[Int]List[AnyVal] 的超类型。

但是说我有以下特点:

trait List[+A] {
  def cons(hd: A): List[A]
}

为什么cons参数类型错误?

为什么需要def cons[B >: A](v: B): List[B]

例如:

val animal_list: List[Animal] = List(tiger, dog)

如果我们调用:

animal_list.cons(tiger)

既然Tiger <: Animal,那么cons不是遇到问题了吗?因为BTigerAAnimalB >: A不是真的。

【问题讨论】:

    标签: scala generics covariance contravariance generic-method


    【解决方案1】:

    为什么cons的参数类型不对?

    trait List[+A] {
      def cons(hd: A): List[A]
    }
    

    编译器给你错误:
    covariant type A occurs in contravariant position in type A of value hd
    因为方法参数算作逆变位置,但A是协变的。

    让我们假设这个方法声明可以编译。然后我们可以这样做:

    class ListImpl[A] extends List[A] {
      override def cons(hd: A): List[A] = ???
    }
    
    val strings: List[String] = new ListImpl[String]
    val values: List[Any] = strings // OK, since List[String] <: List[Any] (in List[A], A is covariant)
    values.cons(13) // OK(??), since values's static type is List[Any], so argument of cons should be Any, and 13 conforms to type Any
    

    上面最后一行真的好吗?我们在values 上致电consvaluesstrings 相同,stringsListImpl[String] 类型的对象。所以最后一行中的cons 调用期待String 参数,但是我们传递了Int,因为values 的静态类型是List[Any]Int 符合Any。这里肯定有问题 - 应该归咎于哪条线?答案是:cons 方法声明。要解决此问题,我们必须从逆变位置(在 cons 声明中)删除协变类型参数 A。或者,我们可以使 A 非协变。

    另请参阅以下问题:#1#2

    ...cons 不是遇到问题了吗?

    trait List[+A] {
      def cons[B >: A](v: B): List[B]
    }
    
    val animal_list: List[Animal] = List(tiger, dog)  // We are assuming that List.apply and concrete implementation of List is somewhere defined.
    

    不,animal_list.cons(tiger) 调用类型正确。

    我假设AnimalDogTiger 的常见超类型,而dogtiger 分别是DogTiger 的实例。

    animal_list.cons(tiger) 调用中,AB 类型参数都实例化为Animal,因此cons 方法采用以下形式:

    def cons[Animal >: Animal](v: Animal): List[Animal]
    

    Animal &gt;: Animal 约束得到满足,因为:

    超类型和子类型关系是自反的,这意味着一个类型 既是其自身的超类型,又是其子类型。 [source]

    cons 的参数是Tiger,符合Animal 类型,因此方法调用是类型正确的。

    请注意,如果您强制将 B 实例化为 Tiger,例如 animal_list.cons[Tiger](tiger),那么此调用将不会是类型正确的,并且您会收到编译器错误。

    查看类似示例here

    【讨论】:

    • 哦,我明白了,所以Tiger 能够传递给cons,尽管Tiger &lt;: Animal 是因为Tiger 首先被类型转换为Animal
    • @testing - 通过声明 def cons[B &gt;: A](v: B): List[B],您可以将任何类型的对象传递给 cons - 类型参数 B 将始终实例化为最精确的常见超类型 A 和类型cons 的论点。将对象传递给需要超类型参数的方法是正常的 OO 事情 - e。 G。当需要Animal 时,您总是可以通过Tiger
    • 如果我有val tiger_list: List[Tiger] = List(tiger) 这意味着tiger_list.con(animal) 是有效的怎么办?我们不应该有类似def cons[B &gt;: A][C &lt;: A](v: B): List[C]
    • @testing - 当您调用List(tiger).cons(animal) 时,B 将被实例化为TigerAnimal 的最精确的通用超类型,即Animal。当您调用List(tiger).cons(7) 时,B 将被实例化为Any,因为它是TigerInt 最精确的常见超类型。如果你仍然感到困惑,那么在 REPL 中运行这段代码,看看 cons 为不同类型的参数返回什么。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-16
    • 2022-01-25
    • 2019-10-02
    • 2012-02-13
    • 1970-01-01
    相关资源
    最近更新 更多