那是因为类型参数是协变的(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
但更改被拒绝,因为它破坏了向后源兼容性并且找不到解决方法。)