首页 > 解决方案 > 在具有协变类型的 Scala 参数化类中实现方法

问题描述

我已经阅读了一些教程,包括有关协变类型的方法签名的主要 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 的超类型。这解决了什么问题?我试图在直观的层面上理解这一点。

标签: scalagenericscovariancecontravariance

解决方案


假设我想制作一个整数列表。并假设,为了论证,这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]

现在,我可以从 a 开始List[Nothing]并添加一个Int。我得到的东西List[Nothing]不再是一个;这是一个List[Int],但我可以做到。如果我接受它List[Int]并稍后再添加一个String,我也可以这样做,但现在我有一个几乎没用的List[Any].


推荐阅读