代码之家  ›  专栏  ›  技术社区  ›  bbarker

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

  •  2
  • bbarker  · 技术社区  · 7 年前

    特别是 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公司 (它应该返回选项类型参数的超类型)。

    1 回复  |  直到 7 年前
        1
  •  1
  •   Andrey Tyukin    6 年前

    不,这并不是“一切正常”,请仔细查看返回值的推断类型。

    宣言 B >: A 意味着:编译器将推断出最具体的类型 B 这样的论点 getOrElse 属于类型 B 同时 A 是的子类型 B 。这是另一种说法:返回类型 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. 不必是的子类型 违约类型 ,这没有任何意义。

    如果你系统地用A、B、C的组合进行所有实验,你会得到以下返回类型:

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

    这本质上是元素全序集上的“最大”函数 C <: B <: A

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