简体   繁体   English

使用协变类型在Scala参数化类中实现方法

[英]Implementing a method inside a Scala parameterized class with a covariant type

I've read a few tutorials including the main Scala documentation regarding method signatures of covariant types. 我已经阅读了一些教程,包括关于协变类型的方法签名的主要Scala文档。 Suppose I have the following abstract 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 + "]"

}

My question concerns the signature of the add() method. 我的问题涉及add()方法的签名。 Why is it necessary to declare it that way? 为什么有必要这样声明? We are passing in a parameter that is a supertype of A. What problem does this solve? 我们传入的参数是A的超类型。这解决了什么问题? I'm trying to understand this on an intuitive level. 我试图在直观的层面上理解这一点。

Suppose I want to make a list of integers. 假设我想要制作一个整数列表。 And suppose, for the sake of argument, that add is implemented without generics. 并且假设,为了论证, add是在没有泛型的情况下实现的。

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

For the sake of this example, let's suppose we have some way of producing an "empty" list. 为了这个例子,让我们假设我们有一些方法来产生一个“空”列表。

def emptyList[A]: List[A] = /* some magic */

Now I want to make my list of integers. 现在我想制作整数列表。

(1 to 10).foldRight(emptyList) { (x, acc) => acc.add(x) }

Oops! 哎呀! We have a problem! 我们出现了问题! When I call emptyList , Scala is going to infer the most general type , and since A is covariant, it's going to assume Nothing . 当我调用emptyList ,Scala会推断出最常见的类型 ,并且因为A是协变的,所以它将假定为Nothing That means I just tried to add an integer to a list of nothing. 这意味着我只是尝试将一个整数添加到一个没有任何内容的列表中。 We could fix this problem with an explicit type signature, 我们可以使用显式类型签名解决此问题,

(1 to 10).foldRight(emptyList[Int]) { (x, acc) => acc.add(x) }

But, really, that's not solving the problem. 但是,实际上,这并没有解决问题。 It adds nothing to readability and just requires the user to do extra work. 它不会增加可读性,只需要用户做额外的工作。 Realistically, I should be able to append a number to a list of nothing. 实际上,我应该可以将一个数字附加到一个没有任何内容的列表中。 It's just that, if I choose to do so, I can't meaningfully call it a list of Nothing anymore. 只是,如果我选择这样做,我就无法将其称为Nothing列表。 Hence, if we define 因此,如果我们定义

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

Now, I can start with a List[Nothing] and add an Int to it. 现在,我可以从List[Nothing]开始,并添加一个Int The thing I get out isn't a List[Nothing] anymore; 我得到的东西不再是List[Nothing]了; it's a List[Int] , but I can do it. 它是List[Int] ,但我可以做到。 If I take that List[Int] and come along later and add a String to it, well I can do that too, but now I have a virtually useless List[Any] . 如果我接受List[Int]并稍后再添加一个String ,那么我也可以这样做,但现在我有一个几乎没用的List[Any]

Formal explanation 正式解释

Given 特定

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 . “这个程序没有编译,因为add的参数元素A类型,我们声明了协变 。这不起作用,因为函数在参数类型中是逆变的 ,在结果类型中是协变的 。为了解决这个问题,我们需要在add翻转参数元素类型的方差
We do this by introducing a new type parameter B that has A as a lower type bound ". 我们通过引入一个新的类型参数B来实现这一点,该参数B具有A作为较低类型的界限 “。
-- reference . - 参考

Intuitive explanation 直观的解释

In this example, if you add something to a List : 在此示例中,如果向List add
It must be an A - in this case the List is still a List[A] . 它必须是A - 在这种情况下, List仍然是List[A]
Or it must be any subtype of A - in this case the element gets upcasted to A , and the List remains a List[A] . 或者它必须是A任何子类型 - 在这种情况下,元素被上传AList仍然是List[A]
Or if it is another type B , then it MUST be a supertype of A - in this case the List gets upcasted to a List[B] . 或者如果它是另一种类型B ,那么它必须是A超类型 - 在这种情况下, List被上传到List[B] (Note: Because Any is just a supertype of everything, in the worst case the List will be upcasted to List[Any] ) . (注意:因为Any只是所有内容的超类型,在最坏的情况下, List将被上传到List[Any]

When you declare +A , you're saying that, for example, List[String] extends List[Object] . 当你声明+A ,你会说,例如, List[String]扩展了List[Object] Now, imagine this: 现在,想象一下:

val ls: List[Object] = List[String]() // Legal because of covariance
ls.add(1) // Adding an int to a list of String?

This is only legal if the type of the List can be expanded to include arbitrary Objects, which is exactly what your add signature does. 只有在可以将List的类型扩展为包含任意对象时,这才是合法的,这正是您的添加签名所做的。 Otherwise, the existence of add(a: A) would imply an inconsistency in the type system. 否则, add(a: A)的存在意味着类型系统中的不一致。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM