1

在对这个问题“ What's the deal with the Either cruft? ”的好回答之后,对于 Either[+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]

编辑
我试图将方差的概念应用于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.

但是有一个问题:为什么我们有 joinLeft 类型Either[C, B1]而不是 just 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])

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


已编辑关于joinLeft of Either
的 另一个问题类型约束和具体化的答案与此相关。


已编辑

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


4

1 回答 1

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

于 2013-04-12T18:28:45.527 回答