协变类型参数

Covariant type parameter

最近我一直在努力进一步加深对 Scala 的理解,但我无法真正理解 covariant/contravariant 类型参数的一些事情。

假设我有一个名为 Basket 的 class,如下所示:

class Basket[+A <: Fruit](items: List[A]) {
  // ...
  def addAll[B >: A <: Fruit](newItems: List[B]): Basket[B] =
new Basket(items ++ newItems)
  // ...
}

还有一些 class 是这样的:

trait Fruit
class Banana extends Fruit
class Orange extends Fruit

我确信这些断言是正确的:

将return一个Basket[Fruit]

将return一个Basket[Banana]


我不明白的是 B >: A 如何影响方法的 return 类型。为什么当我添加 Orange 时 return 类型变成 Basket[Fruit] 当我添加 Banana 时,它仍然是 Basket[Banana] ?它会寻找 "lowest" 普通超类型吗?

是的,Scala 编译器试图找到最低的公共超类型。你可以在 Scala 的任何地方看到它,包括标准库 类。考虑 List 的这个例子,它的参数类型也是协变的:

1.0 :: List(1, 2, 3)
// result type is AnyVal, least common ancestor of Double and Int
res1: List[AnyVal] = List(1.0, 1, 2, 3)

"0" :: List(1, 2, 3)
// result type is List[Any], lowest common ancestor of String and Int
res2: List[Any] = List(0, 1, 2, 3)

0 :: List(1, 2, 3)
// result type is List[Int] exactly
res3: List[Int] = List(0, 1, 2, 3)

res2.head
res4: Any = 0

res2.head.asInstanceOf[String]
res5: String = "0"

有人会说这是 Scala 类型系统的一个可疑特性,因为它很容易以 Any(在我的例子中是这样)或 Product with Serializable(如果你处理案例 类),然后错误消息非常具有误导性。

如果你想限制 Scala 泛化你的类型,你应该使用 "sad in a hat" 类型约束 <:<。那么您的代码将如下所示(为了便于阅读,我已将 类 更改为大小写 类):

case class Banana() extends Fruit
defined class Banana

case class Orange() extends Fruit
defined class Orange

case class Basket[+A <: Fruit](items: List[A]) {
    // ...
    def addAll[B >: A <: Fruit, C >: A](newItems: List[B])(implicit ev: B <:< C): Basket[B] =
  new Basket(items ++ newItems)
    // ...
  }
defined class Basket

val bananaBasket: Basket[Banana] = Basket(List(Banana(), Banana()))
bananaBasket: Basket[Banana] = Basket(List(Banana(), Banana()))

bananaBasket.addAll(List(Orange())) // not accepted
Main.scala:593: Cannot prove that Product with Serializable with cmd27.Fruit <:< cmd47.Banana.
bananaBasket.addAll(List(Orange()))
                   ^
Compilation Failed

bananaBasket.addAll(List(Banana())) // accepted
res52: Basket[Banana] = Basket(List(Banana(), Banana(), Banana()))

您可以在信息丰富的博客 post 中阅读更多关于此模式的信息:http://blog.bruchez.name/2015/11/generalized-type-constraints-in-scala.html