如何理解Scala的选项使用超类型和方差的?

问题描述 投票:2回答:1

具体来说,看着getOrElse

Scala的Option被定义在这样A被协变:

sealed abstract class Option[+A] extends Product with Serializable {
  self =>
  @inline final def getOrElse[B >: A](default: => B): B =
    if (isEmpty) default else this.get
}

getOrElse的定义似乎暗示A的超类型必须返回,这并不一定让很多道理给我。但事实上,它看起来像任何事情都会发生:亚型或超类型。

scala> class A
// defined class A
scala> class B extends A
// defined class B
scala> val optA: Option[A] = Option(null)
val optA: Option[A] = None
scala> optA.getOrElse(new B)
val res23: A = B@66a2c8e7
scala> class C extends B
// defined class C
scala> val optB: Option[B] = Option(null)
val optB: Option[B] = None
scala> optB.getOrElse(new A)
val res24: A = A@2a460bf
scala> optB.getOrElse(new C)
val res25: B = C@e87f97f

这怎么可能,给定的约束?具体地讲,我不明白怎么optB.getOrElse(new C)允许给在getOrElse的约束(它应该返回选项的类型参数的超类型)。

scala types scala-collections variance type-bounds
1个回答
1
投票

不,这不是“一切顺利”,看看推断类型的返回值的接近。

声明B >: A指:编译器将推断最具体类型B使得到getOrElse参数是类型B并同时AB的子类型。这是另一种说法:getOrElse的返回类型是最上限Option两者的参数和回退参数的类型。

你的实验证实了这一点:

scala> class A
scala> class B extends A
scala> class C extends B

scala> val optA: Option[A] = Option(null)
scala> optA.getOrElse(new B)
val res23: A = B@66a2c8e7                 // LUB(A, B) = A

scala> val optB: Option[B] = Option(null)
scala> optB.getOrElse(new A)
val res24: A = A@2a460bf                  // LUB(B, A) = A, symmetric!

scala> optB.getOrElse(new C)
val res25: B = C@e87f97f                  // LUB(B, C) = B

最后一种情况当然是完全有效的,因为new C的类型是C,自此C <: B,它也是类型B的元素。没有矛盾:B是推断返回类型,它不是最具体的参数类型default的(这将是default.type,直接使用时大多是无用的)。特别是,A不必是default.type的亚型,这将没有任何意义。

如果你想系统地做实验用A,B,C的组合,你会得到下面的返回类型:

  | A B C
--+-----
A | A A A
B | A B B
C | A B C

这实质上是对全序设定元素C <: B <: A“最大” - 函数。

简单直观和规则是:编译器和标准库的方法的函数签名确实努力,为客户提供最具体的返回类型,并尽可能多地保留类型信息成为可能。

© www.soinside.com 2019 - 2024. All rights reserved.