【问题标题】:Covariant type parameter协变类型参数
【发布时间】:2016-11-01 08:27:57
【问题描述】:

我最近一直在尝试进一步加深对 Scala 的理解,但我无法真正弄清楚关于协变/逆变类型参数的一些事情。

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

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

还有一些像这样的类:

trait Fruit
class Banana extends Fruit
class Orange extends Fruit

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

  • Basket[Fruit]可以被实例化

  • Basket[String] 无法实例化(因为String 不是Fruit 的子类型)

  • Basket[Banana]Basket[Fruit] 的子类型

  • Basket[Orange]Basket[Fruit] 的子类型

  • 这段代码:

    val bananaBasket: Basket[Banana] = new Basket(List(new Banana, new Banana))
    bananaBasket.addAll(List(new Orange)) 
    

将返回Basket[Fruit]

  • 这段代码:

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

将返回Basket[Banana]


我不明白B &gt;: A 如何影响方法的返回类型。为什么当我添加Orange 时返回类型变为Basket[Fruit] 而当我添加Banana 时,它仍然是Basket[Banana] ?它是否寻找“最低”的普通超类型?

【问题讨论】:

  • 当您尝试编译您的“断言”并查看其中哪些是正确的时,您发现了什么?
  • 您还需要在Basket.addAll方法(addAll[B &gt;: A &lt;: Fruit]...)中将Fruit作为参数B的上限添加。
  • 所有断言都是正确的。我的问题确实不是很清楚。我会编辑它。
  • “它是否寻找“最低”的普通超类型? - 正是
  • @PeterNeyens 仅供参考,您知道为什么我们必须重复约束吗?

标签: scala inheritance types covariance


【解决方案1】:

是的,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”类型约束&lt;:&lt;。然后您的代码将如下所示(为了便于阅读,我已将类更改为案例类):

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-10-30
    • 1970-01-01
    • 2017-05-13
    • 1970-01-01
    • 2012-04-07
    • 1970-01-01
    • 2021-02-16
    • 1970-01-01
    相关资源
    最近更新 更多