使用协变类型在 Scala 参数化 class 中实现方法
Implementing a method inside a Scala parameterized class with a covariant type
我已经阅读了一些教程,包括有关协变类型方法签名的主要 Scala 文档。假设我有以下摘要 class:
abstract class List[+A] {
def head: A
def tail: List[A]
def isEmpty: Boolean
def add[B >: A](element: B): List[B]
protected def printElements: String
override def toString: String = "[" + printElements + "]"
}
我的问题涉及 add()
方法的签名。为什么有必要这样声明呢?我们传入的参数是 A 的超类型。这解决了什么问题?我试图从直觉层面理解这一点。
假设我想制作一个整数列表。并假设,为了论证,add
是在没有泛型的情况下实现的。
def add(element: A): List[A]
为了这个例子,假设我们有一些方法可以生成 "empty" 列表。
def emptyList[A]: List[A] = /* some magic */
现在我想制作我的整数列表。
(1 to 10).foldRight(emptyList) { (x, acc) => acc.add(x) }
糟糕!我们出现了问题!当我调用 emptyList
时,Scala 将推断出 最一般的类型 ,并且由于 A
是协变的,它会假定 Nothing
。这意味着我只是试图将一个整数添加到一个没有任何内容的列表中。我们可以使用显式类型签名解决这个问题,
(1 to 10).foldRight(emptyList[Int]) { (x, acc) => acc.add(x) }
但是,实际上,这并不能解决问题。它不会增加可读性,只需要用户做额外的工作。实际上,我应该能够将一个数字附加到一个没有任何内容的列表中。只是,如果我选择这样做,我就不能再有意义地将它称为 Nothing
的列表了。因此,如果我们定义
def add[B >: A](element: B): List[B]
现在,我可以从 List[Nothing]
开始并向其添加 Int
。我出来的东西不再是 List[Nothing]
;这是一个 List[Int]
,但我可以做到。如果我拿那个 List[Int]
稍后再给它添加一个 String
,我也可以这样做,但现在我有一个几乎没用的 List[Any]
.
当您声明 +A
时,您是在说 List[String]
扩展了 List[Object]
。现在,想象一下:
val ls: List[Object] = List[String]() // Legal because of covariance
ls.add(1) // Adding an int to a list of String?
只有当列表的类型可以扩展为包含任意对象时,这才是合法的,这正是您的添加签名所做的。否则,add(a: A)
的存在将暗示类型系统中的不一致。
正式解释
给定
abstract class List[+A] {
def add(element: A): List[A]
}
"This program does not compile, because the parameter element in add
is of type A
, which we declared covariant. This doesn’t work because functions are contravariant in their parameter types and covariant in their result types. To fix this, we need to flip the variance of the type of the parameter element in add
.
We do this by introducing a new type parameter B
that has A
as a lower type bound".
-- reference.
直观的解释
在这个例子中,如果你 add
一些东西到 List:
它必须是 A
- 在这种情况下 List 仍然是 List[A]
.
或者它必须是 A
的任何 子类型 - 在这种情况下,元素得到 upcasted 到 A
,并且 List 仍然是 List[A]
.
或者,如果它是另一种类型 B
,那么它必须是 A
的 超类型 - 在本例中是 List被升级为 List[B]
。 (注意:因为 Any
只是一切的超类型,在最坏的情况下 List 将被升级为 List[Any]
).
我已经阅读了一些教程,包括有关协变类型方法签名的主要 Scala 文档。假设我有以下摘要 class:
abstract class List[+A] {
def head: A
def tail: List[A]
def isEmpty: Boolean
def add[B >: A](element: B): List[B]
protected def printElements: String
override def toString: String = "[" + printElements + "]"
}
我的问题涉及 add()
方法的签名。为什么有必要这样声明呢?我们传入的参数是 A 的超类型。这解决了什么问题?我试图从直觉层面理解这一点。
假设我想制作一个整数列表。并假设,为了论证,add
是在没有泛型的情况下实现的。
def add(element: A): List[A]
为了这个例子,假设我们有一些方法可以生成 "empty" 列表。
def emptyList[A]: List[A] = /* some magic */
现在我想制作我的整数列表。
(1 to 10).foldRight(emptyList) { (x, acc) => acc.add(x) }
糟糕!我们出现了问题!当我调用 emptyList
时,Scala 将推断出 最一般的类型 ,并且由于 A
是协变的,它会假定 Nothing
。这意味着我只是试图将一个整数添加到一个没有任何内容的列表中。我们可以使用显式类型签名解决这个问题,
(1 to 10).foldRight(emptyList[Int]) { (x, acc) => acc.add(x) }
但是,实际上,这并不能解决问题。它不会增加可读性,只需要用户做额外的工作。实际上,我应该能够将一个数字附加到一个没有任何内容的列表中。只是,如果我选择这样做,我就不能再有意义地将它称为 Nothing
的列表了。因此,如果我们定义
def add[B >: A](element: B): List[B]
现在,我可以从 List[Nothing]
开始并向其添加 Int
。我出来的东西不再是 List[Nothing]
;这是一个 List[Int]
,但我可以做到。如果我拿那个 List[Int]
稍后再给它添加一个 String
,我也可以这样做,但现在我有一个几乎没用的 List[Any]
.
当您声明 +A
时,您是在说 List[String]
扩展了 List[Object]
。现在,想象一下:
val ls: List[Object] = List[String]() // Legal because of covariance
ls.add(1) // Adding an int to a list of String?
只有当列表的类型可以扩展为包含任意对象时,这才是合法的,这正是您的添加签名所做的。否则,add(a: A)
的存在将暗示类型系统中的不一致。
正式解释
给定
abstract class List[+A] {
def add(element: A): List[A]
}
"This program does not compile, because the parameter element in
add
is of typeA
, which we declared covariant. This doesn’t work because functions are contravariant in their parameter types and covariant in their result types. To fix this, we need to flip the variance of the type of the parameter element inadd
.
We do this by introducing a new type parameterB
that hasA
as a lower type bound".
-- reference.
直观的解释
在这个例子中,如果你 add
一些东西到 List:
它必须是 A
- 在这种情况下 List 仍然是 List[A]
.
或者它必须是 A
的任何 子类型 - 在这种情况下,元素得到 upcasted 到 A
,并且 List 仍然是 List[A]
.
或者,如果它是另一种类型 B
,那么它必须是 A
的 超类型 - 在本例中是 List被升级为 List[B]
。 (注意:因为 Any
只是一切的超类型,在最坏的情况下 List 将被升级为 List[Any]
).