joinLeft [A1>:A,B1>:B,C] ...为什么类型约束A1>:A和B1>:B必要?

时间:2013-04-12 16:02:51

标签: scala

对于这个问题“What's the deal with the Either cruft?”的答案很好,对于[+ A,+ B],joinLeft被定义为

joinLeft [A1 >: A, B1 >: B, C] (implicit ev: <:<[A1, Either[C, B1]]):
         Either[C, B1]
@Didier说“A1和B1在技术上是必要的,但并不重要 理解......“;然而,我很好奇为什么我们不能只有

joinLeft[C](implicit ev: <:<[A, Either[C, B]): Either[C, B]

EDITED
我试图将方差的概念应用于joinLeft。首先,joinLeft的来源:

abstract class Either[+A, +B]

def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]):
    Either[C, B1] = this match {
    case Left(a)  => a
    case Right(b) => Right(b)
}

我可能错了,但2ed案例对我来说似乎更容易:
Right(b)需要满足 Either[C, B1] >: Either[C, B]。因为Either是协变的,所以B1 >: B

但是有一个问题:为什么我们有类型Either[C, B1]的joinLeft而不是 只是Either[C, B]?是因为它受到的限制较少吗?

implicit ev: (<:<[A1, Either[C, B1]])只能从A转换 超类型A1到A1的超类型Either[C, B1]。 我认为它在第一种情况下用于a。但我觉得A1不是必需的。 我们可以这样定义joinLeft:

def joinLeft[B1 >: B, C](implicit ev: A <:< Either[C, B1])

?我的分析可能是胡说八道。请随时纠正我。


EDITED
我的另一个问题type constraints and reifications regarding to joinLeft of Either的答案与此问题相关。


已编辑

非常感谢@sschaef跟踪此事。 相关讨论可以在这里找到:


1 个答案:

答案 0 :(得分:3)

这是因为类型参数是协变的(source):

abstract class Either[+A, +B]

解释差异的最简单方法是使用列表:

abstract class MList[A]
case class Cons[A](head: A, tail: MList[A]) extends MList[A]
object MNil extends MList[Nothing]

如果我们尝试实例化这样的列表,它将不起作用:

scala> Cons(1, MNil)
<console>:12: error: type mismatch;
 found   : MNil.type
 required: MList[Int]
Note: Nothing <: Int (and MNil.type <: MList[Nothing]), but class MList is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
              Cons(1, MNil)
                      ^

正如错误消息所述,问题是我们的类型A是不变的,这意味着类型为B的{​​{1}}类型A )说B <: A无效。

我们可以通过声明List[B] <: List[A]协变来改变它:

A

协方差意味着类型的子类型关系被转发到外部类型,在我们的示例中,它意味着abstract class MList[+A] // rest as before scala> Cons(1, MNil) res3: Cons[Int] = Cons(1,MNil$@5ee988c6) 因为MList[Nothing] <: MList[Int]是Scalas的底部类型,因此是每种可能类型的子类型。

但是现在,我们遇到了问题。我们无法向Nothing添加方法,这些方法需要MList类型的参数:

A

为了解释编译器拒绝此代码的原因,有必要深入了解系统。我们假设scala> :paste // Entering paste mode (ctrl-D to finish) abstract class MList[+A] { def Cons(b: A) = new Cons(b, this) } case class Cons[A](head: A, tail: MList[A]) extends MList[A] object MNil extends MList[Nothing] // Exiting paste mode, now interpreting. <console>:11: error: covariant type A occurs in invariant position in type (b: A)Cons[A] of method Cons def Cons(b: A) = new Cons(b, this) ^ <console>:11: error: covariant type A occurs in contravariant position in type A of value b def Cons(b: A) = new Cons(b, this) ^ List[B] <: List[A]始终为真。然后编译以下代码:

B <: A

因为val xs: Array[Any] = Array[Int](18) xs(0) = "hello" Int <: Any也是如此。并且因为Array[Int] <: Array[Any]也是如此,编译器可以毫无问题地翻译此代码,但在运行时它会失败,因为我们无法在String <: Any内存储String(没有对JVM上的数组进行类型擦除)。因此,在Scala中,Array[Int]Array[Int]的分配无效,代码被拒绝。然而,对于Array[Any],我们不会收到错误:

List

不同行为的原因是因为scala> val xs: List[Any] = List[Int](18) xs: List[Any] = List(18) 是协变的,而List不是(它是不变的)。但为什么差异呢?这是因为Array的不可变性,List的元素无法改变。但是对于List我们可以改变元素 - 换句话说,通过差异,程序员可以给编译器提供元素可变或不可信的信息,并且它应该关心没有人用我们的代码做傻事。

我们回到需要修复的Array。因为它是不可变的,所以我们可以安全地将它声明为协变。事实上我们需要这样做,否则我们就无法使用MList。无法为对象提供类型参数,因此为了避免以后出现类型问题,我们需要使用尽可能最低的类型MNil来扩展MList。要解决这个问题,我们需要设置一个下限:

Nothing

abstract class MList[+A] { def Cons[B >: A](b: B) = new Cons(b, this) } case class Cons[A](head: A, tail: MList[A]) extends MList[A] object MNil extends MList[Nothing] scala> MNil Cons 2 Cons 1 res10: Cons[Int] = Cons(1,Cons(2,MNil$@2e6ef76f)) 是我们的下限,意味着B >: AA的子类型或BB的超类型。要获得A MList[Int]MList[Nothing]),我们需要这样的下限,因为MNil我们有Int >: Nothing

所以,这就是MList[Nothing].Cons[Int].Cons[Int]也需要这样下限的原因。实际上,对于joinLeft来说,只有第二个类型绑定joinLeft是必要的,而另一个不需要,因为B1 >: B已经在其第一个类型参数中具有逆变性。这意味着<:<的下限没有任何影响(旁注:我打开了一个删除A下限的拉取请求,但更改被拒绝,因为它向后突破源可以找到兼容性和解决方法。