何时在 Scala 方法中使用下限

when to use lower bound in scala method

我一直在复习 Scala 知识,一直在绕 variance/lower 界。

'functional programming in scala'书中,Either类型,它有以下signature/exercise(flatMap的实现版本,orElse在Either上运行在右边值)。

sealed trait Either[+E,+A] {
 def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = ???
 def orElse[EE >: E, B >: A](b: => Either[EE, B]): Either[EE, B] = ???
}

这本书的注释说

when mapping over the right side, we must promote the left type parameter to some supertype, to satisfy the +E variance annotation. similarly for 'orElse'

我的问题是:

  1. 为什么不是我们必须在flatMap函数中说B >: A?我们不需要满足 +A?
  2. 为什么 orElse 签名需要 B >: A

我理解方法参数算作逆变位置,所以我们不可能在方法参数中包含 AE。即 fb 的 'return type' 中不能有 EA

也许我遗漏了一些关于 subtyping/lower bound/function 作为参数的基础知识。

请用一些具体的例子帮助我理解它。

p.s。大多数关于方差或 upper/lower 边界的文章,我发现 class/trait.

中只有 1 个类型参数

why do not we have to say B >: A in the flatMap function? we do not need to satisfy +A?

flatMap 不会对 f: A => Either[EE, B] 生成的类型施加任何限制。这意味着,例如,我们可以有一个 Either[Throwable, String] 并使用 flatMap 将其转换为 Either[Throwable, Int]。请注意 StringInt 之间的唯一关系是通过 Any.

why does orElse signature requires B >: A

当我们说:"Give me the left hand side, or else give me the right hand side"时,我们通常希望两种类型对齐,这样我们的"fallback",通过orElse,将提供一个有意义的回退。

例如,让我们使用上面的例子,假设我们想要一个 Either[Throwable, String] 并使用 flatMap:

将其转换为 Either[Throwable, Int]
val either: Either[Throwable, String] = Right("42")
val res: Either[Throwable, Int] = either.flatMap(str => Try(str.toInt).toEither)

当我们的 String 为 42 时,这将起作用,但如果它不是有效的 Int,我们将返回 Left[Throwable]。现在让我们决定,如果解析失败,我们总是希望 return -1 作为默认值(当然有更好的方法来对此建模,但坚持我的看法)。为此,我们可以利用 orElse

val either: Either[Throwable, String] = Right("42")
val res: Either[Throwable, Int] = either.flatMap(str => Try(str.toInt).toEither).orElse(Right(-1))

这样,LHS 和 RHS 之间的关系得以保留,我们收到一个合理的值作为结果。如果 B 根本不受限于 A,我们通常会在类型层次结构中得到一个超类型,例如 AnyRefAny.

关于 EE >: E 约束的另一件事。由于 E 协变的 ,如果我们尝试将其用作 flatMap 函数的类型参数:

sealed trait Either[+E, +A] {
    def flatMap[B](f: A => Either[E, B]): Either[E, B] = ???
}

编译器会对我们大喊:

Error:(7, 20) covariant type E occurs in contravariant position in type A => Either[E,B] of value f def flatMap[B](f: A => Either[E, B]): Either[E, B] = ???

那是因为协变类型不能"go in"到方法中,只能用在return类型中,而逆变类型参数"go in"相反,不能用在return类型中结果类型。

如果 Either 不变,签名将是

sealed trait Either[E,A] {
  def flatMap[B](f: A => Either[E, B]): Either[E, B] = ???
  def orElse(b: => Either[E, A]): Either[E, A] = ???
}

这里AB没有联系

现在如果我们使 EitherE 协变,我们必须添加 EE >: E

sealed trait Either[+E,A] {
  def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] = ???
  def orElse[EE >: E](b: => Either[EE, A]): Either[EE, A] = ???
}

否则,如果我们使 Either 关于 A 协变,我们必须添加 AA >: A

sealed trait Either[E,+A] {
  def flatMap[B](f: A => Either[E, B]): Either[E, B] = ???
  def orElse[AA >: A](b: => Either[E, AA]): Either[E, AA] = ???
}

只是 AA 表示为 B

在实际情况下,Either 对于两种类型参数都是协变的,因此这是上述的组合。

我想现在很明显 flatMap 中的 BorElse 中的 B 是不同的。