Skip to content

Commit e5c5561

Browse files
gagandeepkalratravisbrown
authored andcommitted
backported #3146 Added redeem and redeemWith
1 parent 1d82711 commit e5c5561

File tree

5 files changed

+124
-15
lines changed

5 files changed

+124
-15
lines changed

core/src/main/scala/cats/instances/future.scala

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,47 @@
11
package cats
22
package instances
33

4-
import scala.util.control.NonFatal
54
import scala.concurrent.{ExecutionContext, Future}
5+
import scala.util.control.NonFatal
66

77
trait FutureInstances extends FutureInstances1 {
88

99
implicit def catsStdInstancesForFuture(
1010
implicit ec: ExecutionContext
1111
): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] =
1212
new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] with StackSafeMonad[Future] {
13-
def pure[A](x: A): Future[A] = Future.successful(x)
13+
def pure[A](x: A): Future[A] =
14+
Future.successful(x)
15+
16+
def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] =
17+
fa.flatMap(f)
1418

15-
def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f)
19+
def handleErrorWith[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] =
20+
fea.recoverWith { case t => f(t) }
1621

17-
def handleErrorWith[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] = fea.recoverWith { case t => f(t) }
22+
def raiseError[A](e: Throwable): Future[A] =
23+
Future.failed(e)
1824

19-
def raiseError[A](e: Throwable): Future[A] = Future.failed(e)
20-
override def handleError[A](fea: Future[A])(f: Throwable => A): Future[A] = fea.recover { case t => f(t) }
25+
override def handleError[A](fea: Future[A])(f: Throwable => A): Future[A] =
26+
fea.recover { case t => f(t) }
2127

2228
override def attempt[A](fa: Future[A]): Future[Either[Throwable, A]] =
2329
(fa.map(a => Right[Throwable, A](a))).recover { case NonFatal(t) => Left(t) }
2430

25-
override def recover[A](fa: Future[A])(pf: PartialFunction[Throwable, A]): Future[A] = fa.recover(pf)
31+
override def recover[A](fa: Future[A])(pf: PartialFunction[Throwable, A]): Future[A] =
32+
fa.recover(pf)
2633

2734
override def recoverWith[A](fa: Future[A])(pf: PartialFunction[Throwable, Future[A]]): Future[A] =
2835
fa.recoverWith(pf)
2936

30-
override def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f)
37+
override def map[A, B](fa: Future[A])(f: A => B): Future[B] =
38+
fa.map(f)
3139

32-
override def catchNonFatal[A](a: => A)(implicit ev: Throwable <:< Throwable): Future[A] = Future(a)
40+
override def catchNonFatal[A](a: => A)(implicit ev: Throwable <:< Throwable): Future[A] =
41+
Future(a)
3342

34-
override def catchNonFatalEval[A](a: Eval[A])(implicit ev: Throwable <:< Throwable): Future[A] = Future(a.value)
43+
override def catchNonFatalEval[A](a: Eval[A])(implicit ev: Throwable <:< Throwable): Future[A] =
44+
Future(a.value)
3545
}
3646
}
3747

core/src/main/scala/cats/instances/try.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package cats
22
package instances
33

4-
import TryInstances.castFailure
4+
import cats.instances.TryInstances.castFailure
55

6-
import scala.util.control.NonFatal
7-
import scala.util.{Failure, Success, Try}
86
import scala.annotation.tailrec
7+
import scala.util.{Failure, Success, Try}
98

109
trait TryInstances extends TryInstances1 {
1110

@@ -69,7 +68,7 @@ trait TryInstances extends TryInstances1 {
6968
ta.recover { case t => f(t) }
7069

7170
override def attempt[A](ta: Try[A]): Try[Either[Throwable, A]] =
72-
(ta.map(a => Right[Throwable, A](a))).recover { case NonFatal(t) => Left(t) }
71+
ta match { case Success(a) => Success(Right(a)); case Failure(e) => Success(Left(e)) }
7372

7473
override def recover[A](ta: Try[A])(pf: PartialFunction[Throwable, A]): Try[A] =
7574
ta.recover(pf)

core/src/main/scala/cats/syntax/applicativeError.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,35 @@ final class ApplicativeErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal
100100
def recoverWith(pf: PartialFunction[E, F[A]])(implicit F: ApplicativeError[F, E]): F[A] =
101101
F.recoverWith(fa)(pf)
102102

103+
/**
104+
* Returns a new value that transforms the result of the source,
105+
* given the `recover` or `map` functions, which get executed depending
106+
* on whether the result is successful or if it ends in error.
107+
*
108+
* This is an optimization on usage of [[attempt]] and [[Functor.map]],
109+
* this equivalence being available:
110+
*
111+
* {{{
112+
* fa.redeem(fe, fs) <-> fa.attempt.map(_.fold(fe, fs))
113+
* }}}
114+
*
115+
* Usage of `redeem` subsumes [[handleError]] because:
116+
*
117+
* {{{
118+
* fa.redeem(fe, id) <-> fa.handleError(fe)
119+
* }}}
120+
*
121+
*
122+
* @see [[MonadErrorOps.redeemWith]], [[attempt]] and [[handleError]]
123+
*
124+
* @param recover is the function that gets called to recover the source
125+
* in case of error
126+
* @param f is the function that gets to transform the source
127+
* in case of success
128+
*/
129+
def redeem[B](recover: E => B, f: A => B)(implicit F: ApplicativeError[F, E]): F[B] =
130+
F.handleError(F.map(fa)(f))(recover)
131+
103132
def onError(pf: PartialFunction[E, F[Unit]])(implicit F: ApplicativeError[F, E]): F[A] =
104133
F.onError(fa)(pf)
105134

core/src/main/scala/cats/syntax/monadError.scala

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,40 @@ final class MonadErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal {
2929

3030
def adaptError(pf: PartialFunction[E, E])(implicit F: MonadError[F, E]): F[A] =
3131
F.adaptError(fa)(pf)
32+
33+
/**
34+
* Returns a new value that transforms the result of the source,
35+
* given the `recover` or `bind` functions, which get executed depending
36+
* on whether the result is successful or if it ends in error.
37+
*
38+
* This is an optimization on usage of [[ApplicativeError.attempt]] and [[FlatMap.flatMap]],
39+
* this equivalence being available:
40+
*
41+
* {{{
42+
* fa.redeemWith(fe, fs) <-> fa.attempt.flatMap(_.fold(fe, fs))
43+
* }}}
44+
*
45+
* Usage of `redeemWith` subsumes [[ApplicativeError.handleErrorWith]] because:
46+
*
47+
* {{{
48+
* fa.redeemWith(fe, F.pure) <-> fa.handleErrorWith(fe)
49+
* }}}
50+
*
51+
* Usage of `redeemWith` also subsumes [[FlatMap.flatMap]] because:
52+
*
53+
* {{{
54+
* fa.redeemWith(F.raiseError, fs) <-> fa.flatMap(fs)
55+
* }}}
56+
*
57+
* @see [[ApplicativeErrorOps.redeem]], [[ApplicativeError.attempt]] and [[ApplicativeError.handleErrorWith]]
58+
*
59+
* @param recover is the function that gets called to recover the source
60+
* in case of error
61+
* @param bind is the function that gets to transform the source
62+
* in case of success
63+
*/
64+
def redeemWith[B](recover: E => F[B], bind: A => F[B])(implicit F: MonadError[F, E]): F[B] =
65+
F.flatMap(F.attempt(fa))(_.fold(recover, bind))
3266
}
3367

3468
final class MonadErrorRethrowOps[F[_], E, A](private val fea: F[Either[E, A]]) extends AnyVal {

tests/src/test/scala/cats/tests/SyntaxSuite.scala

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ object SyntaxSuite
386386
val done = a.tailRecM[F, B](a => returnValue)
387387
}
388388

389-
def testApplicativeError[F[_, _], E, A](implicit F: ApplicativeError[F[E, *], E]): Unit = {
389+
def testApplicativeError[F[_, _], E, A, B](implicit F: ApplicativeError[F[E, *], E]): Unit = {
390390
type G[X] = F[E, X]
391391

392392
val e = mock[E]
@@ -409,12 +409,49 @@ object SyntaxSuite
409409

410410
val pfegea = mock[PartialFunction[E, G[A]]]
411411
val gea4 = ga.recoverWith(pfegea)
412+
413+
val eb = mock[E => B]
414+
val ab = mock[A => B]
415+
val gb: G[B] = gea.redeem(eb, ab)
412416
}
413417

414418
def testApplicativeErrorSubtype[F[_], A](implicit F: ApplicativeError[F, CharSequence]): Unit = {
415419
val fea = "meow".raiseError[F, A]
416420
}
417421

422+
def testMonadError[F[_, _], E, A, B](implicit F: MonadError[F[E, *], E]): Unit = {
423+
type G[X] = F[E, X]
424+
425+
val e = mock[E]
426+
val ga = e.raiseError[G, A]
427+
428+
val gea = mock[G[A]]
429+
430+
val ea = mock[E => A]
431+
val gea1 = ga.handleError(ea)
432+
433+
val egea = mock[E => G[A]]
434+
val gea2 = ga.handleErrorWith(egea)
435+
436+
val gxea = ga.attempt
437+
438+
val gxtea = ga.attemptT
439+
440+
val pfea = mock[PartialFunction[E, A]]
441+
val gea3 = ga.recover(pfea)
442+
443+
val pfegea = mock[PartialFunction[E, G[A]]]
444+
val gea4 = ga.recoverWith(pfegea)
445+
446+
val eb = mock[E => B]
447+
val ab = mock[A => B]
448+
val gb: G[B] = gea.redeem(eb, ab)
449+
450+
val efb = mock[E => G[B]]
451+
val afb = mock[A => G[B]]
452+
val gb2: G[B] = gea.redeemWith(efb, afb)
453+
}
454+
418455
def testNested[F[_], G[_], A]: Unit = {
419456
val fga: F[G[A]] = mock[F[G[A]]]
420457

0 commit comments

Comments
 (0)