那是因为类型参数是协变的(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类型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]因为Nothing是 Scalas 底层类型,因此是每个可能类型的子类型。
但是现在,我们有一个问题。我们不能向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也是如此,编译器可以毫无问题地翻译此代码,但在运行时它会失败,因为我们无法将 a 存储String在 an 内部Array[Int](JVM 上的数组没有类型擦除)。因此,在 Scala 中,将 a 赋值Array[Int]给 anArray[Any]是无效的,代码被拒绝。尽管如此,因为List我们没有收到错误:
scala> val xs: List[Any] = List[Int](18)
xs: List[Any] = List(18)
不同行为的原因是因为List是协变的,而Array不是(它是不变的)。但为什么会有差异?那是因为 的不可变性质List, a 的元素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 >: A是我们的下界,meansA是 的子类型B或者B是 的超类型A。要得到 aMList[Int]当我们有 a MList[Nothing](即MNil),我们需要这样一个下界,因为Int >: Nothing我们有MList[Nothing].Cons[Int].Cons[Int]。
所以,这就是为什么joinLeft也需要这样的下限的原因。事实上,确实joinLeft只有第二种类型的界B1 >: B是必要的,而另一种是不需要的,因为<:<它的第一个类型参数已经是逆变的。这意味着下限 onA没有任何影响(旁注:我打开了一个拉取请求,删除了下限A但更改被拒绝,因为它破坏了向后源兼容性并且找不到解决方法。)