diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 4a151c0b36..97e8264250 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -77,6 +77,33 @@ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { def redeemWith[A, B](fa: F[A])(recover: E => F[B], bind: A => F[B]): F[B] = flatMap(attempt(fa))(_.fold(recover, bind)) + /** + * Reifies the value or error of the source and performs an effect on the result, + * then recovers the original value or error back into `F`. + * + * Note that if the effect returned by `f` fails, the resulting effect will fail too. + * + * Alias for `fa.attempt.flatTap(f).rethrow` for convenience. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * scala> import scala.util.{Try, Success, Failure} + * + * scala> def checkError(result: Either[Throwable, Int]): Try[String] = result.fold(_ => Failure(new java.lang.Exception), _ => Success("success")) + * + * scala> val a: Try[Int] = Failure(new Throwable("failed")) + * scala> a.attemptTap(checkError) + * res0: scala.util.Try[Int] = Failure(java.lang.Exception) + * + * scala> val b: Try[Int] = Success(1) + * scala> b.attemptTap(checkError) + * res1: scala.util.Try[Int] = Success(1) + * }}} + */ + def attemptTap[A, B](fa: F[A])(f: Either[E, A] => F[B]): F[A] = + rethrow(flatTap(attempt(fa))(f)) + override def adaptError[A](fa: F[A])(pf: PartialFunction[E, E]): F[A] = recoverWith(fa)(pf.andThen(raiseError[A] _)) } diff --git a/core/src/main/scala/cats/syntax/monadError.scala b/core/src/main/scala/cats/syntax/monadError.scala index f098d3c6cc..fa75dff02f 100644 --- a/core/src/main/scala/cats/syntax/monadError.scala +++ b/core/src/main/scala/cats/syntax/monadError.scala @@ -32,6 +32,9 @@ final class MonadErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal { def redeemWith[B](recover: E => F[B], bind: A => F[B])(implicit F: MonadError[F, E]): F[B] = F.redeemWith[A, B](fa)(recover, bind) + + def attemptTap[B](f: Either[E, A] => F[B])(implicit F: MonadError[F, E]): F[A] = + F.attemptTap(fa)(f) } final class MonadErrorRethrowOps[F[_], E, A](private val fea: F[Either[E, A]]) extends AnyVal {