【问题标题】:Implicit resolution with covariance具有协方差的隐式解析
【发布时间】:2015-06-08 04:48:56
【问题描述】:

为什么下面的代码在Foo 不变时有效,但在协变时无效? Foo 的协变版本会产生一个类型错误,指出在调用 useF1 时,参数的类型为 Foo[T] 但需要 F1useF2 会产生类似的错误。

如果从Foo 中删除方差注释,则代码有效。针对F1 的模式匹配暴露了T = Int 的事实,因此x 具有Foo[Int] 类型。隐式转换函数用于将Foo[Int] 转换为F1useF1 的参数中。 F2 也是如此。当 Foo 是协变时,这个过程的哪一部分不同,为什么?

// A GADT with two constructors
sealed abstract class Foo[+T]
final case class F1() extends Foo[Int]
final case class F2() extends Foo[Unit]

object Example {
  // A Foo[Int] can only be an F1
  implicit def refineGADT(x : Foo[Int]) : F1 = x.asInstanceOf[F1]
  // A Foo[Unit] can only be an F2
  implicit def refineGADT(x : Foo[Unit]) : F2 = x.asInstanceOf[F2]

  def useF1(x : F1) = ()
  def useF2(x : F2) = ()

  def demo[T](x : Foo[T]) = x match {
    case F1() => useF1(x) // error
    case F2() => useF2(x) // error
  }
}

虽然 GADT 通常会使子类型变得更加复杂,但在这种情况下,仅有的两种可能的具体类型是 Foo[Int]Foo[Unit],它们之间不存在子类型关系,因此子类型不应影响此示例。

【问题讨论】:

    标签: scala variance implicits


    【解决方案1】:

    首先,让我们简化您的示例(假设我们忽略类型擦除):

    class Foo[+T] 
    
    def demo[T](x : Foo[T]) = x match {
      case _: Foo[Int] => x: Foo[Int] //error but works with `class Foo[T]`
      case _: Foo[Unit] => x: Foo[Unit] //error but works with `class Foo[T]`
    }
    

    甚至:

    class Foo[T]
    scala> def demo[T](x : Foo[T]) = x match {case _: Foo[Int] => x}
    demo: [T](x: Foo[T])Foo[Int] //notice Int
    
    class Foo[+T]
    scala> def demo[T](x : Foo[T]) = x match {case _: Foo[Int] => x}
    demo: [T](x: Foo[T])Foo[T] //notice T
    

    x 作为表达式的预期类型是存在类型Foo[_ >: T](作为应用于返回类型的协方差的结果),或者更准确地说是Foo[X >: T] forSome{type X}。因此编译器无法处理它,因为此功能或错误(匹配上下文中的类型转换)不适用于存在类型,因为它无法证明Foo[Int] 始终属于R,其中R :> Foo[X] 用于某些@ 987654329@。所以R 可能是:> Foo[Int] 或可能是:> Foo[Any] 或其他:> Foo[_ :> Int],这使得R 成为可能范围的副产品。这样的副产品不能转换为Foo[Int]

    class Foo[T] 
    
    def demo(x : Foo[_]) = x match {
      case _: Foo[Int] => x: Foo[Int] //error
      case _: Foo[Unit] => x: Foo[Unit] //error 
    }                 
    
    <console>:9: error: type mismatch;
     found   : Foo[_$1] where type _$1
     required: Foo[Int]
                 case a: Foo[Int] => x: Foo[Int] //error
                                     ^
                            ^
    
    <console>:10: error: type mismatch;
     found   : Foo[_$1] where type _$1
     required: Foo[Unit]
                 case a: Foo[Unit] => x: Foo[Unit] //error
                                      ^
    

    附:关于协方差如何与存在性相关的示例:

    scala> class Foo[T]
    defined class Foo
    
    scala> def demo[T](x : Foo[T]) = (x: Foo[_ >: Int]) //can't cast to something in Int..Any range
    <console>:17: error: type mismatch;
     found   : Foo[T]
     required: Foo[_ >: Int]
    Note: T <: Any, but class Foo is invariant in type T.
    You may wish to define T as +T instead. (SLS 4.5)
           def demo[T](x : Foo[T]) = (x: Foo[_ >: Int])
                                      ^
    
    scala> class Foo[+T]
    defined class Foo
    
    scala> def demo[T](x : Foo[T]) = (x: Foo[_ >: Int])
    demo: [T](x: Foo[T])Foo[Any]
    

    【讨论】:

    • 我没有关注 x : Foo[T] 如何变成 x : Foo[_ &gt;: T]。如果我尝试应用协方差的定义,我会得到相反的界限。协方差意味着S &lt;: T 意味着F[S] &lt;: F[T]。因此,F[T] 包括 F[S] 的任何子类型 STx : Foo[_ &lt;: T]。您的最后一个示例显示可以转换为不太精确的类型Foo[U],其中T &lt;: UFoo[_ &gt;: Int]Foo[Int] 类型的行为不同:new Foo[Unit] 的结果可以保存在 x 中,如果 x 具有前一种类型,但不是后者。
    • x : Foo[_ &lt;: T] - 这取决于你从哪一边看。当您查看输入时,demo(x : Foo[T]) - x 可以小于 Foo[T]。当您查看所需的输出时 - 可以将 x 转换为大于 Foo[T] 的任何值。如果没有协方差,这个“更大”可能是Foo[T]..Any。使用协方差有“双”范围Foo[T]..Any,其中T 可能是T..Any 之一,因此case _: Foo[Int] =&gt; x 的返回类型预计为&gt;: Foo[T] forSome {type T &lt;: Any} = Foo[_ &gt;: T]
    • 因此编译器必须检查Foo[Int] 是否始终属于R,其中R :&gt; Foo[X] 用于某些X &gt;: Int - 所以R 可能是:&gt; Foo[Int] 或可能是:&gt; Foo[Any] 或别的东西:&gt; Foo[_ :&gt; Int],这使得R 成为可能范围的副产品。如果没有协方差,它必须检查 Foo[Int] 是否始终属于 R 其中 R :&gt; Foo[Int]
    【解决方案2】:

    您只需在demo 中重新绑定匹配的F1F2

    def demo[T](x : Foo[T]) = x match {
      case y@F1() => useF1(y)
      case y@F2() => useF2(y)
    }
    

    x 的类型为Foo[T],因此编译器无法推断它是useF1 的有效输入。当T 不变时,编译器可以再推导一点点,就能解决这种情况。当T是协变的,我们知道它在匹配的情况下是有效的,但是我们必须绑定一个新的标识符来帮助编译器。您甚至可以将匹配的类绑定到x 以隐藏外部x(例如case x@F1()),但是如果需要,您将无法引用外部x

    【讨论】:

    • 这样可以避免类型错误。不过,我仍然希望有人能解释为什么方差注释会阻止这些函数调用的类型推断。
    猜你喜欢
    • 1970-01-01
    • 2021-04-07
    • 2021-04-09
    • 1970-01-01
    • 2020-01-28
    • 1970-01-01
    • 2018-08-28
    • 2017-06-30
    • 1970-01-01
    相关资源
    最近更新 更多