From 2e649935b4fa38d2f0199a34119d3c244fb164f6 Mon Sep 17 00:00:00 2001 From: Ben Plommer Date: Sun, 13 Jan 2019 20:33:26 +0000 Subject: [PATCH 1/3] add `adaptErr` to `ApplicativeErrorOps`, make `MonadError#adaptError` an alias for it --- core/src/main/scala/cats/MonadError.scala | 6 +++++- .../src/main/scala/cats/syntax/applicativeError.scala | 11 +++++++++++ .../main/scala/cats/laws/ApplicativeErrorLaws.scala | 10 ++++++++++ laws/src/main/scala/cats/laws/MonadErrorLaws.scala | 6 ------ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 6b8b4ddfb1..bdb55b1503 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -1,5 +1,7 @@ package cats +import cats.syntax.ApplicativeErrorOps + /** * A monad that also allows you to raise and or handle an error value. * @@ -38,9 +40,11 @@ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { * scala> 1.asRight[String].adaptError(pf) * res2: Either[String,Int] = Right(1) * }}} + * + * Will be moved to ApplicativeError in Cats 2.0: see [[https://github.com/typelevel/cats/issues/2685]] */ 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)) + new ApplicativeErrorOps(fa).adaptErr(pf)(this) /** * Inverse of `attempt` diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index 2f379ad151..923c8d227c 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -97,4 +97,15 @@ final class ApplicativeErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal def orElse(other: => F[A])(implicit F: ApplicativeError[F, E]): F[A] = F.handleErrorWith(fa)(_ => other) + + /** + * Transform certain errors using `pf` and rethrow them. + * Non matching errors and successful values are not affected by this function. + * + * This is the same as `MonadErrorOps#adaptError`. It cannot have the same name because + * this would result in ambiguous implicits. `adaptError` will be moved from `MonadError` + * to `ApplicativeError` in Cats 2.0: see [[https://github.com/typelevel/cats/issues/2685]] + */ + def adaptErr(pf: PartialFunction[E, E])(implicit F: ApplicativeError[F, E]): F[A] = + F.recoverWith(fa)(pf.andThen(F.raiseError)) } diff --git a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala index ddc66a6d27..6c8d1dd911 100644 --- a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala @@ -45,6 +45,16 @@ trait ApplicativeErrorLaws[F[_], E] extends ApplicativeLaws[F] { def onErrorRaise[A](fa: F[A], e: E, fb: F[Unit]): IsEq[F[A]] = F.onError(F.raiseError[A](e)) { case err => fb } <-> F.map2(fb, F.raiseError[A](e))((_, b) => b) + + def adaptErrorPure[A](a: A, f: E => E): IsEq[F[A]] = { + import cats.syntax.applicativeError._ + F.pure(a).adaptErr { case x => f(x) } <-> F.pure(a) + } + + def adaptErrorRaise[A](e: E, f: E => E): IsEq[F[A]] = { + import cats.syntax.applicativeError._ + F.raiseError[A](e).adaptErr { case x => f(x) } <-> F.raiseError(f(e)) + } } object ApplicativeErrorLaws { diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index 6912ffd88a..ce86988405 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -14,12 +14,6 @@ trait MonadErrorLaws[F[_], E] extends ApplicativeErrorLaws[F, E] with MonadLaws[ def monadErrorEnsureOrConsistency[A](fa: F[A], e: A => E, p: A => Boolean): IsEq[F[A]] = F.ensureOr(fa)(e)(p) <-> F.flatMap(fa)(a => if (p(a)) F.pure(a) else F.raiseError(e(a))) - def adaptErrorPure[A](a: A, f: E => E): IsEq[F[A]] = - F.adaptError(F.pure(a)) { case x => f(x) } <-> F.pure(a) - - def adaptErrorRaise[A](e: E, f: E => E): IsEq[F[A]] = - F.adaptError(F.raiseError[A](e)) { case x => f(x) } <-> F.raiseError(f(e)) - def rethrowAttempt[A](fa: F[A]): IsEq[F[A]] = F.rethrow(F.attempt(fa)) <-> fa } From 4e2f3ddd72a777439926549d1b598eadc0abd1f1 Mon Sep 17 00:00:00 2001 From: Ben Plommer Date: Sat, 26 Jan 2019 13:14:37 +0000 Subject: [PATCH 2/3] revert changes to ApplicativeErrorLaws/MonadErrorLaws --- .../main/scala/cats/laws/ApplicativeErrorLaws.scala | 10 ---------- laws/src/main/scala/cats/laws/MonadErrorLaws.scala | 6 ++++++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala index 6c8d1dd911..ddc66a6d27 100644 --- a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala @@ -45,16 +45,6 @@ trait ApplicativeErrorLaws[F[_], E] extends ApplicativeLaws[F] { def onErrorRaise[A](fa: F[A], e: E, fb: F[Unit]): IsEq[F[A]] = F.onError(F.raiseError[A](e)) { case err => fb } <-> F.map2(fb, F.raiseError[A](e))((_, b) => b) - - def adaptErrorPure[A](a: A, f: E => E): IsEq[F[A]] = { - import cats.syntax.applicativeError._ - F.pure(a).adaptErr { case x => f(x) } <-> F.pure(a) - } - - def adaptErrorRaise[A](e: E, f: E => E): IsEq[F[A]] = { - import cats.syntax.applicativeError._ - F.raiseError[A](e).adaptErr { case x => f(x) } <-> F.raiseError(f(e)) - } } object ApplicativeErrorLaws { diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index ce86988405..6912ffd88a 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -14,6 +14,12 @@ trait MonadErrorLaws[F[_], E] extends ApplicativeErrorLaws[F, E] with MonadLaws[ def monadErrorEnsureOrConsistency[A](fa: F[A], e: A => E, p: A => Boolean): IsEq[F[A]] = F.ensureOr(fa)(e)(p) <-> F.flatMap(fa)(a => if (p(a)) F.pure(a) else F.raiseError(e(a))) + def adaptErrorPure[A](a: A, f: E => E): IsEq[F[A]] = + F.adaptError(F.pure(a)) { case x => f(x) } <-> F.pure(a) + + def adaptErrorRaise[A](e: E, f: E => E): IsEq[F[A]] = + F.adaptError(F.raiseError[A](e)) { case x => f(x) } <-> F.raiseError(f(e)) + def rethrowAttempt[A](fa: F[A]): IsEq[F[A]] = F.rethrow(F.attempt(fa)) <-> fa } From 77608094a8cd8f0a328ec2acda8da14a3c7d0097 Mon Sep 17 00:00:00 2001 From: Ben Plommer Date: Sat, 26 Jan 2019 13:25:54 +0000 Subject: [PATCH 3/3] replace cross-dependency with duplication --- core/src/main/scala/cats/MonadError.scala | 8 ++++---- .../scala/cats/syntax/applicativeError.scala | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index bdb55b1503..6a1ebdce82 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -1,7 +1,5 @@ package cats -import cats.syntax.ApplicativeErrorOps - /** * A monad that also allows you to raise and or handle an error value. * @@ -41,10 +39,12 @@ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { * res2: Either[String,Int] = Right(1) * }}} * - * Will be moved to ApplicativeError in Cats 2.0: see [[https://github.com/typelevel/cats/issues/2685]] + * The same function is available in `ApplicativeErrorOps` as `adaptErr` - it cannot have the same + * name because this would result in ambiguous implicits. `adaptError` will be moved from MonadError to + * ApplicativeError in Cats 2.0: see [[https://github.com/typelevel/cats/issues/2685]] */ def adaptError[A](fa: F[A])(pf: PartialFunction[E, E]): F[A] = - new ApplicativeErrorOps(fa).adaptErr(pf)(this) + recoverWith(fa)(pf.andThen(raiseError)) /** * Inverse of `attempt` diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index 923c8d227c..19791ce915 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -102,6 +102,22 @@ final class ApplicativeErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal * Transform certain errors using `pf` and rethrow them. * Non matching errors and successful values are not affected by this function. * + * Example: + * {{{ + * scala> import cats._, implicits._ + * + * scala> def pf: PartialFunction[String, String] = { case "error" => "ERROR" } + * + * scala> "error".asLeft[Int].adaptErr(pf) + * res0: Either[String,Int] = Left(ERROR) + * + * scala> "err".asLeft[Int].adaptErr(pf) + * res1: Either[String,Int] = Left(err) + * + * scala> 1.asRight[String].adaptErr(pf) + * res2: Either[String,Int] = Right(1) + * }}} + * * This is the same as `MonadErrorOps#adaptError`. It cannot have the same name because * this would result in ambiguous implicits. `adaptError` will be moved from `MonadError` * to `ApplicativeError` in Cats 2.0: see [[https://github.com/typelevel/cats/issues/2685]]