Scala中的协变方法参数

时间:2019-06-28 10:33:24

标签: scala

{{3}}的最高答案解释了为什么方法参数是互变的。作者说,如果这样可以编译:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend(elem: T): ListNode[T] =
    ListNode(elem, this)
}

那没关系:

val list1: ListNode[String] = ListNode("abc", null)
val list2: ListNode[Any] = list1  // list2 and list1 are the same thing
val list3: ListNode[Int] = list2.prepend(1) // def prepend(elem: T): ListNode[T]

请注意,我已删除了原始代码段的最后一行。

我的思考过程如下:

  1. 在第1行上,名为ListNode[String]的{​​{1}}被分配了一个新的list1。这里没什么奇怪的。 ListNode(someString, null)不是list1
  2. 在第2行上,ListNode[String]被分配了ListNode[Any]。这很好,因为list1是协变的,而ListNodeAny的超类型。
  3. 在第3行上,调用String的{​​{1}}方法。由于prependlist2,因此list2应该返回ListNode[Any]。但是方法调用的结果已分配给list2.prepend()。这可能无法编译,因为ListNode[Any]ListNode[Int]子类型,而Int却不是协变的!

我误解了吗?作者如何宣称将进行编译?

2 个答案:

答案 0 :(得分:-1)

让我们考虑一下,如果我们定义如下:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend(elem: T): ListNode[T] =
    ListNode(elem, this)
}

如果您查看prepend方法,则以T(它是ListNode的类型参数)作为参数,然后返回ListNode[T],就这么简单。现在,让我们详细说明一下用法:

val list1: ListNode[String] = ListNode("abc", null)

在上述情况下,Stringnull的超类型,并且由于ListNode被定义为covarient,因此是正确的。

val list2: ListNode[Any] = list1

在第二种情况下,ListNode[String]被分配给ListNode[Any],因为AnyString的超类型,这也是正确的。

val list3: ListNode[Int] = list2.prepend(1)

最后,在上述第三种情况下,我们要做的是在1之前加上Int。如果您查看方法prepend(elem: T): ListNode[T],我们将传递Int作为elem值的类型,并返回ListNode类型的T;在这种情况下,类型为ListNode的{​​{1}}。因此,从调用Int返回的值是list2.prepend(1)类型。因此,ListNode[Int]的上述执行也是可能的(理论上是正确的)。

但是,在scala中,您无法定义list3类型的def prepend(elem: T): ListNode[T]您会遇到编译错误),因此您将永远无法执行{{ 1}}。但是,您可以使用下限,如下所示:

covariant

答案 1 :(得分:-3)

单独考虑prepend方法:

def prepend(elem: T): ListNode[T]

此签名意味着,如果您给它一个T,则会得到ListNode[T]。如果您给它Int,则会得到ListNode[Int],因此您所质疑的作业是完全有效的。

还请记住,作者说ListNode的这个版本未编译,所以这是一种假设情况。