在带有协变类型的Scala参数化类中实现方法

时间:2019-01-12 21:01:12

标签: scala generics covariance contravariance

我阅读了一些教程,其中包括有关协变类型的方法签名的主要Scala文档。假设我有以下抽象类:

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的超类型。这可以解决什么问题?我正在尝试从直觉上理解这一点。

3 个答案:

答案 0 :(得分:3)

假设我要列出一个整数列表。并出于争论的原因,假设add是在没有泛型的情况下实现的。

def add(element: A): List[A]

为了这个例子,让我们假设我们有某种方式来产生一个“空”列表。

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]

答案 1 :(得分:3)

形式说明

给予

abstract class List[+A] {
  def add(element: A): List[A]
}
  

“该程序无法编译,因为add中的参数 element 的类型为A,我们将其声明为 covariant 。无法使用,因为功能的参数类型为互变,结果类型为协变。要解决此问题,我们需要翻转 add中参数 element 类型的方差
  为此,我们引入了一个新的类型参数B,该参数以A作为下类型约束”。
  -reference

直观的解释

在此示例中,如果您将某个内容add {<1>} :
它必须是A-在这种情况下,列表仍然是List[A]
或它必须是A的任何子类型-在这种情况下,该元素被更新A,并且列表仍然是List[A]
或者,如果它是另一种类型B,则它必须是A超类型-在这种情况下,列表被向上投射到{{ 1}}。 (注意:由于List[B]只是所有内容的超类型,因此在最坏的情况下,列表将被投射到Any

>

答案 2 :(得分:1)

声明from elasticsearch import Elasticsearch ... Elasticsearch(... 时,是说+A扩展了List[String]。现在,想象一下:

List[Object]

仅当List的类型可以扩展为包括任意Object时,这才合法,这正是您的添加签名所做的。否则,val ls: List[Object] = List[String]() // Legal because of covariance ls.add(1) // Adding an int to a list of String? 的存在将暗示类型系统中的不一致。