说我有以下功能:
case class ErrA(msg: String)
case class ErrB(msg: String)
def doA(): Either[ErrA, Int] = Right(2)
def doB(): Either[ErrB, Int] = Right(3)
ErrA
和ErrB
是不相关的类型,实际上并未在此示例之外的同一文件中声明。它们不能轻易地从常见类型继承。
我想介绍一种代表两种错误类型的新类型:
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
并且因为类型ErrA
和ErrAOrB
无关,它能做的最好的事情就是使泛型有意义的是使用与预期类型不兼容的Object
。
答案 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
不会。