具有协方差的隐式解决方案

时间:2015-04-03 04:51:20

标签: scala variance implicits

为什么Foo不变时,以下代码有效,但是当它是协变时却不行? Foo的协变版本会产生类型错误,表示在useF1的调用中,参数的类型为Foo[T],但F1是必需的。为useF2生成了类似的错误。

如果从Foo中删除了方差注释,则代码可以正常工作。与F1匹配的模式暴露了T = Int的事实,因此x具有类型Foo[Int]。隐式转换函数用于在Foo[Int]的参数中将F1转换为useF1。同样适用于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],并且它们之间没有子类型关系,所以子类型不应该影响这个例子

2 个答案:

答案 0 :(得分:0)

您只需重新绑定F1中匹配的F2demo

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

答案 1 :(得分:0)

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

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] 1}}对于某些X >: Int。因此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
                                  ^

P.S。关于协方差如何与存在性相关的例子:

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]