Scala中Error特质flatMap方法协变类型编译错误原因咨询
先来看你定义的代码:
sealed trait Error[+E, +A] { final def map[B](f: A => B): Error[E, B] = { this match { case Ok(a) => Ok(f(a)) case Fail(e) => Fail(e) } } final def flatMap[B](f: A => Error[E, B]): Error[E, B] = { this match { case Ok(a) => f(a) case Fail(e) => Fail(e) } } } object Error { final case class Ok[A](a: A) extends Error[Nothing, A] final case class Fail[E](e: E) extends Error[E, Nothing] }
编译时触发的错误:
Error.scala:12: covariant type E occurs in contravariant position in type A => returnerror.Error[E,B] of value f
final def flatMap[B](f: A => Error[E, B]): Error[E, B] = {
^
one error found
错误原因解析
核心问题在于Scala的方差规则:你给Error的类型参数E标记了协变(+E),这意味着如果E1是E2的子类型,Error[E1, A]可以被当作Error[E2, A]使用(更具体的错误可以替代宽泛的错误,符合直觉)。
但协变类型参数有个严格限制:它只能出现在输出位置(比如方法返回值、val字段),不能出现在输入位置(方法参数、var字段)。而你的flatMap方法参数f: A => Error[E, B]里,E的位置刚好违反了这个规则:
- 函数类型
A => Error[E, B]本身在返回值Error[E, B]上是协变的,但站在trait Error[+E, +A]的角度,这个函数是方法的输入参数——换句话说,E通过函数的返回值间接出现在了方法的输入位置,而协变参数不允许出现在输入位置。
举个更直观的例子:假设E1 <: E2(比如FileNotFound <: IOException),那么Error[E1, A] <: Error[E2, A]。如果Error[E1, A]的flatMap接受A => Error[E1, B],当我们把它当作Error[E2, A]使用时,它应该接受A => Error[E2, B]——但函数类型是逆变的,A => Error[E2, B]不能替代A => Error[E1, B],这就产生了类型矛盾,所以编译器报错。
修复方案
最常用的修复方式是给flatMap添加一个下界约束的类型参数,允许函数返回更宽泛的错误类型,符合协变的规则:
sealed trait Error[+E, +A] { final def map[B](f: A => B): Error[E, B] = { this match { case Ok(a) => Ok(f(a)) case Fail(e) => Fail(e) } } // 添加E1 >: E的下界约束 final def flatMap[E1 >: E, B](f: A => Error[E1, B]): Error[E1, B] = { this match { case Ok(a) => f(a) case Fail(e) => Fail(e) // E是E1的子类型,所以Fail(e)可以向上转为Error[E1, B] } } } object Error { final case class Ok[A](a: A) extends Error[Nothing, A] final case class Fail[E](e: E) extends Error[E, Nothing] }
这样修改后,编译器就会通过:因为我们允许flatMap接受返回更宽泛错误类型的函数,而原Fail(e)中的e: E是E1的子类型,自然可以被当作Error[E1, B]的实例,完美契合协变的类型规则。
内容的提问来源于stack exchange,提问作者ryskajakub

