协变类型参数
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
我确信这些断言是正确的:
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))
将return一个Basket[Fruit]
这段代码:
val bananaBasket: Basket[Banana] = new Basket(List(new Banana, new Banana))
bananaBasket.addAll(List(new Banana))
将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
最近我一直在努力进一步加深对 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
我确信这些断言是正确的:
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))
将return一个Basket[Fruit]
这段代码:
val bananaBasket: Basket[Banana] = new Basket(List(new Banana, new Banana)) bananaBasket.addAll(List(new Banana))
将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