After spending several hours on this I am fairly certain that, as of March 2019, this function is not implemented in cats directly. However, the already existing catsDataMonadErrorFForEitherT
monad does make it possible to implement it in a mostly uncomplicated manner.
implicit class EitherTFutureAdditions[A, B](et: EitherT[Future, A, B]) {
val me = EitherT.catsDataMonadErrorFForEitherT[Future, Throwable, A]
def recoverLeft(pf: PartialFunction[Throwable, A]): EitherT[Future, A, B] =
me.recoverWith[B](et) { case t: Throwable =>
EitherT.fromEither[Future](Left(pf(t)))
}
}
I am uncertain what the performance implications of constructing the monad within the generic implicit class are, but it works. If you don't need the generic case, you may want to replace [A, B]
with explicit types.
While I was at it I also wrote recoverWithFlat
, handleErrorLeft
, and handleErrorWithFlat
and packaged it all into a file EitherTUtils.scala
// Place this in a new file and then use it like so:
//
// import EitherTUtils.EitherTFutureAdditions
//
// val et: EitherT[Future, String, Int] =
// EitherT(Future.failed[Either[String, Int]](new Exception("example")))
// et recoverLeft {
// case e: Exception => s"Failed with reason ${e.getMessage}"
// }
//
object EitherTUtils {
/**
* Convenience additions for recovering and handling Future.failed within an EitherT
*
* @see [[cats.ApplicativeError]] for recover, recoverWith, handleError, handleErrorWith, and attemptT
*
* @param et a Futured EitherT
* @tparam A the Either's left type
* @tparam B the Either's right type
*/
implicit class EitherTFutureAdditions[A, B](et: EitherT[Future, A, B]) {
val me = EitherT.catsDataMonadErrorFForEitherT[Future, Throwable, A]
/**
* Recover from certain errors from this EitherT's Future (if failed) by mapping them to the EitherT's
* left value.
*
* @see [[recoverWithFlat]] for mapping to an Either[Future, A, B]
*
* @see [[handleErrorWithFlat]] to handle any/all errors.
*/
def recoverLeft(pf: PartialFunction[Throwable, A]): EitherT[Future, A, B] =
me.recoverWith[B](et) {
case t: Throwable =>
EitherT.fromEither[Future](Left(pf(t)))
}
/**
* Recover from certain errors from this EitherT's Future (if failed) by mapping them to the EitherT's
* value.
*
* @see [[recoverLeft]] for mapping to an EitherT's left value.
*
* @see [[handleErrorWithFlat]] to handle any/all errors.
*/
def recoverWithFlat(pf: PartialFunction[Throwable, Either[A, B]]): EitherT[Future, A, B] =
me.recoverWith[B](et) {
case t: Throwable =>
EitherT.fromEither[Future](pf(t))
}
/**
* Handle any error from this EitherT's Future (if failed) by mapping them to the EitherT's left value.
*
* @see [[recoverWithFlat]] for handling only certain errors
*
* @see [[handleErrorLeft]] for mapping to the EitherT's left value
*/
def handleErrorLeft(pf: PartialFunction[Throwable, A]): EitherT[Future, A, B] =
me.handleErrorWith[B](et) { t =>
EitherT.fromEither[Future](Left[A, B](pf(t)))
}
/**
* Handle any error from this EitherT's Future (if failed) by mapping them to the EitherT's value.
*
* @see [[recoverWithFlat]] for handling only certain errors
*
* @see [[handleErrorLeft]] for mapping to the EitherT's left value
*/
def handleErrorWithFlat(pf: PartialFunction[Throwable, Either[A, B]]): EitherT[Future, A, B] =
me.handleErrorWith[B](et) { t =>
EitherT.fromEither[Future](pf(t))
}
}
}
I thought that these might be my first contribution to cats, but after several hours of navigating the library's layout I realized the modifications would be non trivial and I don't have the knowledge level yet to submit them in a manner that wouldn't require significant work from other project contributors.
I may try again once I better understand the cats library structure.