Подумайте о создании типа данных алгебры c из ошибок, например
sealed abstract class Error(val message: String)
case class ErrorA(msg: String) extends Error(msg)
case class ErrorB(msg: String) extends Error(msg)
case class ErrorC(msg: String) extends Error(msg)
, а затем измените левую часть возвращаемых \/
на Error
import scalaz.\/
import scalaz.syntax.either._
object ModuleA {
def getA: Error \/ String = "1".right
}
object ModuleB {
def getB(s: String): Error \/ Int = ErrorB("boom").left
}
object ModuleC {
def getC(s: String, i: Int): Error \/ Long = 1L.right
}
for {
s <- ModuleA.getA
i <- ModuleB.getB(s)
l <- ModuleC.getC(s, i)
} yield l
, что дает
res0: Error \/ Long = -\/(ErrorB(boom))
Если вы не можете создать ADT, рассмотрите leftMap
, чтобы изменить тип ошибки на общий тип, например
case class ErrorWrapper(m: String)
for {
s <- ModuleA.getA.leftMap { e: ModuleA.ErrorA => ErrorWrapper(e.msg) }
i <- ModuleB.getB(s).leftMap { e: ModuleB.ErrorB => ErrorWrapper(e.msg) }
l <- ModuleC.getC(s, i).leftMap { e: ModuleC.ErrorC => ErrorWrapper(e.msg) }
} yield l
// res0: ErrorWrapper \/ Long = -\/(ErrorWrapper(boom))
или, может быть, даже необычно, через структурную типизацию
implicit class CommonErrorWrapper[A <: Product { def msg: String }](e: A) {
def toErrorWrapper: ErrorWrapper = ErrorWrapper(e.msg)
}
for {
s <- ModuleA.getA.leftMap(_.toErrorWrapper)
i <- ModuleB.getB(s).leftMap(_.toErrorWrapper)
l <- ModuleC.getC(s, i).leftMap(_.toErrorWrapper)
} yield l
// res1: ErrorWrapper \/ Long = -\/(ErrorWrapper(boom))
leftMap
полезно не только для изменения типа ошибки, но также мы можем обогатить ошибку, добавив локально доступную контекстную информацию.
Примечание * Монадный преобразователь EitherT
может использоваться, когда форма имеет тип F[A \/ B]
, например, Future[Error \/ B]
, однако в вашем случае это просто A \/ B
, следовательно, EitherT
это не может быть правильным инструментом. Смежный вопрос EitherT с несколькими типами возврата