对于这个问题“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]
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跟踪此事。
相关讨论可以在这里找到:
答案 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 >: A
是A
的子类型或B
是B
的超类型。要获得A
MList[Int]
(MList[Nothing]
),我们需要这样的下限,因为MNil
我们有Int >: Nothing
。
所以,这就是MList[Nothing].Cons[Int].Cons[Int]
也需要这样下限的原因。实际上,对于joinLeft
来说,只有第二个类型绑定joinLeft
是必要的,而另一个不需要,因为B1 >: B
已经在其第一个类型参数中具有逆变性。这意味着<:<
的下限没有任何影响(旁注:我打开了一个删除A
下限的拉取请求,但更改被拒绝,因为它向后突破源可以找到兼容性和解决方法。)