Ease awkward constructions when doing something before raising
Opened this issue · 1 comments
morgen-peschke commented
A common pattern shows up in our code that doesn't seem to have an elegant solution: we'd like to log before raising an error (usually from an Option
).
Currently, there are a couple ways to accomplish this for Option
values (in rough order of increasing subjective elegance):
fooAlgebra.lookupOpt(id).flatMap {
case Some(foo) => foo.pure[F]
case None => log("Relevant context") >> CannotProcess.raiseError[F, Foo]
}
fooAlgebra.lookupOpt(id).flatMap {
_.fold(log("Relevant context") >> CannotProcess.raiseError[F, Foo])(_.pure[F])
}
fooAlgebra.lookupOpt(id).flatMap {
_.map(_.pure[F]).getOrElse(log("Relevant context") >> CannotProcess.raiseError[F, Foo])
}
fooAlgebra.lookupOpt(id)
.flatTap(result => log("Relevant context").whenA(result.isEmpty))
.flatMap(_.liftTo[F](CannotProcess))
The situation for Either
(or Validated
) is much better, but still not great:
fooAlgebra.lookupE(id).flatMap {
case Right(foo) => foo.pure[F]
case Left(FooNotFound) => log("Relevant context A") >> CannotProcess.raiseError[F, Foo]
case Left(_) => log("Relevant context B") >> ServerError.raiseError[F, Foo]
}
fooAlgebra.lookupE(id).flatMap(_.fold(
{
case FooNotFound => log("Relevant context A") >> CannotProcess.raiseError[F, Foo]
case _ => log("Relevant context B") >> ServerError.raiseError[F, Foo]
},
_.pure[F]
))
fooAlgebra.lookupE(id)
.flatMap(_.leftTraverse {
case FooNotFound => log("Relevant context A").as(CannotProcess).widen[LogicError]
case _ => log("Relevant context B").as(ServerError).widen[LogicError]
})
.flatMap(_.liftTo[F])
I propose adding variants of ApplicativeError#from*
to MonadError
and their equivalent variants of the liftTo
helpers in OptionSyntax
EitherSyntax
and ValidatedSyntax
, which would allow writing the above as:
fooAlgebra.lookupOpt(id).flatMap(_.raiseNone {
log("Relevant context").as(CannotProcess).widen[LogicError]
})
fooAlgebra.lookupE(id)
.flatMap(_.leftMap[F[LogicError]] {
case FooNotFound => log("Relevant context A").as(CannotProcess).widen
case _ => log("Relevant context B").as(ServerError).widen
})
.flatMap(_.raiseLeft)