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

时间:2018-04-08 12:11:46

标签: scala types scala-collections variance type-bounds

具体来说,请看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(它应该返回Option的类型参数的超类型)。

1 个答案:

答案 0 :(得分:1)

不,它不是"一切顺利",仔细观察返回值的推断类型。

声明B >: A表示:编译器将推断出最具体的类型B,使getOrElse的参数类型为B,同时AB的子类型。这是另一种说法:getOrElse的返回类型是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 C的类型为C,而C <: B后,也是类型的元素{ {1}}。没有矛盾:B是推断的返回类型,它不是最具体的参数类型B(这将是default,直接使用时大多没用。特别是,default.type不一定是A的子类型,这没有任何意义。

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

default.type

这基本上是具有元素 | A B C --+----- A | A A A B | A B B C | A B C 的完全有序集合上的&#34;最大&#34; -function。

简单&amp;直观的规则是:编译器和标准库中方法的函数签名很难提供最具体的返回类型,并尽可能多地保留类型信息。