diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index 5b0dc72b08..c336a81025 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -72,6 +72,16 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { */ def attemptT[A](fa: F[A]): EitherT[F, E, A] = EitherT(attempt(fa)) + /** + * Returns a new value that transforms the result of the source, + * given the `recover` or `map` functions, which get executed depending + * on whether the result is successful or if it ends in error. + * + * This is an optimization on usage of [[attempt]] and [[map]]. + */ + def redeem[A, B](fa: F[A])(recover: E => B, mapper: A => B): F[B] = + map(attempt(fa))(_.fold(recover, mapper)) + /** * Recover from certain errors by mapping them to an `A` value. * diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index f861f82803..3df6f5b08b 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -42,6 +42,16 @@ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { def adaptError[A](fa: F[A])(pf: PartialFunction[E, E]): F[A] = flatMap(attempt(fa))(_.fold(e => raiseError(pf.applyOrElse[E, E](e, _ => e)), pure)) + /** + * Returns a new value that transforms the result of the source, + * given the `recover` or `bind` functions, which get executed depending + * on whether the result is successful or if it ends in error. + * + * This is an optimization on usage of [[attempt]] and [[flatMap]]. + */ + def redeemWith[A, B](fa: F[A])(recover: E => F[B], bind: A => F[B]): F[B] = + flatMap(attempt(fa))(_.fold(recover, bind)) + /** * Inverse of `attempt` * diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index 5d2f088235..fa5f1a7be1 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -84,6 +84,9 @@ final class ApplicativeErrorOps[F[_], E, A](val fa: F[A]) extends AnyVal { def attemptT(implicit F: ApplicativeError[F, E]): EitherT[F, E, A] = F.attemptT(fa) + def redeem[B](recover: E => B, map: A => B)(implicit F: ApplicativeError[F, E]): F[B] = + F.redeem(fa)(recover, map) + def recover(pf: PartialFunction[E, A])(implicit F: ApplicativeError[F, E]): F[A] = F.recover(fa)(pf) diff --git a/core/src/main/scala/cats/syntax/monadError.scala b/core/src/main/scala/cats/syntax/monadError.scala index 19f2c58432..0e081d0f3d 100644 --- a/core/src/main/scala/cats/syntax/monadError.scala +++ b/core/src/main/scala/cats/syntax/monadError.scala @@ -23,6 +23,9 @@ final class MonadErrorOps[F[_], E, A](val fa: F[A]) extends AnyVal { def adaptError(pf: PartialFunction[E, E])(implicit F: MonadError[F, E]): F[A] = F.adaptError(fa)(pf) + + def redeemWith[B](recover: E => F[B], bind: A => F[B])(implicit F: MonadError[F, E]): F[B] = + F.redeemWith(fa)(recover, bind) } final class MonadErrorRethrowOps[F[_], E, A](val fea: F[Either[E, A]]) extends AnyVal {