monadic 中的隐式转换以在 Scala 中理解

Implicit conversion in monadic for comprehension in Scala

假设我有以下功能:

case class ErrA(msg: String)

case class ErrB(msg: String)

def doA(): Either[ErrA, Int] = Right(2)

def doB(): Either[ErrB, Int] = Right(3)

ErrAErrB 是不相关的类型,实际上并未在此示例之外的同一文件中声明。它们不容易从通用类型继承。

我想介绍一种可以表示两种错误类型的新类型:

sealed trait ErrAOrB

case class ErrAWrapper(e: ErrA) extends ErrAOrB

case class ErrBWrapper(e: ErrB) extends ErrAOrB

然后使用 for comprehension 编写以下函数:

def doAplusB(): Either[ErrAOrB, Int] =
  for (a <- doA().right;
       b <- doB().right) yield a + b

有没有办法让编译器将那些特定的 Either 类型隐式转换为常见的 Either[ErrAOrB, Int] 类型?

例如:

implicit def wrapA[T](res: Either[ErrA, T]): Either[ErrAOrB, T] = res.left.map(ErrAWrapper(_))

implicit def wrapB[T](res: Either[ErrB, T]): Either[ErrAOrB, T] = res.left.map(ErrBWrapper(_))

但这不起作用,因为隐式转换仅应用于 for 理解中的最终表达式,然后编译器必须将其绑定到 doA,因为类型 ErrAErrAOrB 是无关的它能做的最好的事情就是使泛型有意义是使用与预期类型不兼容的 Object

在 Scala 中不推荐使用隐式视图,从定义 wrapA/wrapB 时编译器的功能警告中可以看出。

即使您通过 Either.RightProjection 而不是 Either 定义隐式视图来抓住机会 - 想象一下人们阅读您的代码并想知道 Either[ErrA, Int] 是如何变成 Either[ErrAOrB, Int] ?没有 IDE 提示,所以没有找到 wrapA 隐式

的好方法

因此,请改用隐式 class:

implicit class WrapA[T](x: Either[ErrA, T]) {
  def wrap = x.left.map(ErrAWrapper(_)): Either[ErrAOrB, T]
}

implicit class WrapB[T](x: Either[ErrB, T]) {
  def wrap = x.left.map(ErrBWrapper(_)): Either[ErrAOrB, T]
}

scala> def doAplusB(): Either[ErrAOrB, Int] =
     |   for {
     |      a <- doA().wrap.right
     |      b <- doB().wrap.right
     |   } yield a + b
doAplusB: ()Either[ErrAOrB,Int]

P.S。如果您的计算是独立的(如您的示例),则 + 操作不需要 monad - Applicative 就足够了。例如,看看 cats.data.Validated 或 scalaz.Validation。


回答是否可能欺骗 scalac:

implicit def wrapBB[T](res: Either.RightProjection[ErrB, T]): Either.RightProjection[ErrAOrB, T] = res.e.left.map(ErrBWrapper(_)).right

implicit def wrapAA[T](res: Either.RightProjection[ErrA, T]): Either.RightProjection[ErrAOrB, T] = res.e.left.map(ErrAWrapper(_)).right

def doAplusB(): Either[ErrAOrB, Int] =
  for (a <- doA().right: Either.RightProjection[ErrAOrB, Int] ;
       b <- doB().right: Either.RightProjection[ErrAOrB, Int]) yield a + b

但这需要 Either.RightProjection 类型归属,但如果你有一些并行赋值(应用式而不是理解式),我相信具有 ad-hoc 超类型的东西可以工作。

甚至(根据您的 wrapB 定义):

implicit def wrapAA[T] ...
implicit def wrapBB[T] ...

implicit def wrapB[T](res: Either[ErrB, T]): Either[ErrAOrB, T] = res.left.map(ErrBWrapper(_))

def doAplusB(): Either[ErrAOrB, Int] =
  for (a <- doA().right:  Either.RightProjection[ErrAOrB, Int];
       b <- doB().right) yield a + b

原因是扩展到:

doA().right.flatMap(a => doB().right.map(b => a + b))

flatMap 要求返回 RightProjection,但 map 不需要。