在Scala

时间:2016-11-08 15:23:03

标签: scala monads implicit-conversion for-comprehension implicits

说我有以下功能:

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 comprehension中的最终表达式,然后编译器必须将其绑定到doA并且因为类型ErrAErrAOrB无关,它能做的最好的事情就是使泛型有意义的是使用与预期类型不兼容的Object

1 个答案:

答案 0 :(得分:2)

在Scala中不建议使用隐式视图,因为在定义wrapA / wrapB时可以从编译器的功能警告中看到。

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

因此,请改用隐式类:

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进行+操作 - 应用就足够了。例如,查看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类型的归属,但是如果你有一些并行赋值(Applicative-style而不是for-comprehension),我相信具有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不会。