如何理解 Scala 的 Option 中超类型和变体的使用?

How to understand the use of supertypes and variance in Scala's Option?

具体看getOrElse.

Scala 的 OptionA 中被定义为协变的,如下所示:

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 的超类型必须 returned,这对我来说不一定很有意义。但事实上,看起来任何事情都会发生:子类型或超类型。

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

考虑到限制,这怎么可能?具体来说,鉴于 getOrElse 的约束,我不明白如何允许 optB.getOrElse(new C) (它应该 return 选项类型参数的超类型)。

不,不是 "everything goes",仔细看看 return 值的推断类型。

声明 B >: A 意味着:编译器将推断最具体的类型 B 这样 getOrElse 的参数是 B 类型并且同时 AB 的子类型。这是另一种说法:getOrElse的return类型是Option的参数和Option的类型的最小上限 fallback-argument.

您的实验证实了这一点:

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 CC类型,而C <: B又是也是 B 类型的元素。没有矛盾:B是推断的return类型,它不是参数的最具体类型default(这将是 default.type,直接使用时几乎没有用)。特别是,A 不必是 default.type 的子类型,这没有任何意义。

如果您系统地使用 A、B、C 的组合进行所有实验,您将得到以下 return 类型:

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

这本质上是 "maximum" 元素在完全有序集合上的函数 C <: B <: A

简单直观的规则是:编译器和标准库中方法的函数签名非常努力地提供最具体的 return 类型,并尽可能多地保留类型信息。