From 5676e11311ad4f1b7d2c8a49200c4e349ad2490a Mon Sep 17 00:00:00 2001 From: Greg Wogan-Browne Date: Wed, 14 Mar 2018 16:08:10 -0700 Subject: [PATCH 1/3] Add ensureP method to MonadError --- core/src/main/scala/cats/MonadError.scala | 6 +++ .../main/scala/cats/syntax/monadError.scala | 3 ++ .../scala/cats/tests/MonadErrorSuite.scala | 45 ++++++++++++++----- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index f861f82803..476bb0ceac 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -19,6 +19,12 @@ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { def ensureOr[A](fa: F[A])(error: A => E)(predicate: A => Boolean): F[A] = flatMap(fa)(a => if (predicate(a)) pure(a) else raiseError(error(a))) + /** + * Turns a successful value into an error specified by the partial function if it is defined for the value. + */ + def ensureP[A](fa: F[A])(pf: PartialFunction[A, E]): F[A] = + flatMap(fa)(a => if (pf.isDefinedAt(a)) raiseError(pf(a)) else pure(a)) + /** * Transform certain errors using `pf` and rethrow them. * Non matching errors and successful values are not affected by this function. diff --git a/core/src/main/scala/cats/syntax/monadError.scala b/core/src/main/scala/cats/syntax/monadError.scala index 75b4dbe001..4922226cd1 100644 --- a/core/src/main/scala/cats/syntax/monadError.scala +++ b/core/src/main/scala/cats/syntax/monadError.scala @@ -16,6 +16,9 @@ final class MonadErrorOps[F[_], E, A](val fa: F[A]) extends AnyVal { def ensureOr(error: A => E)(predicate: A => Boolean)(implicit F: MonadError[F, E]): F[A] = F.ensureOr(fa)(error)(predicate) + def ensureP(pf: PartialFunction[A, E])(implicit F: MonadError[F, E]): F[A] = + F.ensureP(fa)(pf) + def adaptError(pf: PartialFunction[E, E])(implicit F: MonadError[F, E]): F[A] = F.adaptError(fa)(pf) } diff --git a/tests/src/test/scala/cats/tests/MonadErrorSuite.scala b/tests/src/test/scala/cats/tests/MonadErrorSuite.scala index 9c9c66d615..c0168667e2 100644 --- a/tests/src/test/scala/cats/tests/MonadErrorSuite.scala +++ b/tests/src/test/scala/cats/tests/MonadErrorSuite.scala @@ -1,36 +1,57 @@ package cats package tests +import scala.util.{Failure, Success, Try} + class MonadErrorSuite extends CatsSuite { - val successful: Option[Int] = 42.some - val failed: Option[Int] = None + val successful: Try[Int] = Success(42) + val failedValue: Throwable = new IllegalArgumentException("default failure") + val otherValue: Throwable = new IllegalStateException("other failure") + val failed: Try[Int] = Failure(failedValue) test("ensure raises an error if the predicate fails") { - successful.ensure(())(i => false) should === (None) + successful.ensure(failedValue)(_ => false) should === (failed) } test("ensure returns the successful value if the predicate succeeds") { - successful.ensure(())(i => true) should === (successful) + successful.ensure(failedValue)(_ => true) should === (successful) } - test("ensure returns the failure, when applied to a failure") { - failed.ensure(())(i => false) should === (failed) - failed.ensure(())(i => true) should === (failed) + test("ensure returns the original failure, when applied to a failure") { + failed.ensure(otherValue)(_ => false) should === (failed) + failed.ensure(otherValue)(_ => true) should === (failed) } test("ensureOr raises an error if the predicate fails") { - successful.ensureOr(_ => ())(_ => false) should === (None) + successful.ensureOr(_ => failedValue)(_ => false) should === (failed) } test("ensureOr returns the successful value if the predicate succeeds") { - successful.ensureOr(_ => ())(_ => true) should === (successful) + successful.ensureOr(_ => failedValue)(_ => true) should === (successful) + } + + test("ensureOr returns the original failure, when applied to a failure") { + failed.ensureOr(_ => otherValue)(_ => false) should === (failed) + failed.ensureOr(_ => otherValue)(_ => true) should === (failed) } - test("ensureOr returns the failure, when applied to a failure") { - failed.ensureOr(_ => ())(_ => false) should === (failed) - failed.ensureOr(_ => ())(_ => true) should === (failed) + test("ensureP returns the successful value if the partial function is not defined") { + successful.ensureP { + case i if i < 0 => failedValue + } should === (successful) } + test("ensureP returns the original failure, when applied to a failure") { + failed.ensureP { + case i if i < 0 => otherValue + } should === (failed) + } + + test("ensureP raises an error if the partial function is defined") { + successful.ensureP { + case i if i > 0 => failedValue + } should === (failed) + } } From 321db59cba7d7387a325cb093525d2fcb3397814 Mon Sep 17 00:00:00 2001 From: gbrowne Date: Wed, 14 Mar 2018 20:54:40 -0700 Subject: [PATCH 2/3] Rename to `reject`. Fix Test. --- core/src/main/scala/cats/MonadError.scala | 6 ------ core/src/main/scala/cats/syntax/monadError.scala | 4 ++-- tests/src/test/scala/cats/tests/MonadErrorSuite.scala | 8 +++++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 476bb0ceac..f861f82803 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -19,12 +19,6 @@ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { def ensureOr[A](fa: F[A])(error: A => E)(predicate: A => Boolean): F[A] = flatMap(fa)(a => if (predicate(a)) pure(a) else raiseError(error(a))) - /** - * Turns a successful value into an error specified by the partial function if it is defined for the value. - */ - def ensureP[A](fa: F[A])(pf: PartialFunction[A, E]): F[A] = - flatMap(fa)(a => if (pf.isDefinedAt(a)) raiseError(pf(a)) else pure(a)) - /** * Transform certain errors using `pf` and rethrow them. * Non matching errors and successful values are not affected by this function. diff --git a/core/src/main/scala/cats/syntax/monadError.scala b/core/src/main/scala/cats/syntax/monadError.scala index 4922226cd1..4e34493255 100644 --- a/core/src/main/scala/cats/syntax/monadError.scala +++ b/core/src/main/scala/cats/syntax/monadError.scala @@ -16,8 +16,8 @@ final class MonadErrorOps[F[_], E, A](val fa: F[A]) extends AnyVal { def ensureOr(error: A => E)(predicate: A => Boolean)(implicit F: MonadError[F, E]): F[A] = F.ensureOr(fa)(error)(predicate) - def ensureP(pf: PartialFunction[A, E])(implicit F: MonadError[F, E]): F[A] = - F.ensureP(fa)(pf) + def reject(pf: PartialFunction[A, E])(implicit F: MonadError[F, E]): F[A] = + F.flatMap(fa)(a => if (pf.isDefinedAt(a)) F.raiseError(pf(a)) else F.pure(a)) def adaptError(pf: PartialFunction[E, E])(implicit F: MonadError[F, E]): F[A] = F.adaptError(fa)(pf) diff --git a/tests/src/test/scala/cats/tests/MonadErrorSuite.scala b/tests/src/test/scala/cats/tests/MonadErrorSuite.scala index c0168667e2..c607dfe97e 100644 --- a/tests/src/test/scala/cats/tests/MonadErrorSuite.scala +++ b/tests/src/test/scala/cats/tests/MonadErrorSuite.scala @@ -5,6 +5,8 @@ import scala.util.{Failure, Success, Try} class MonadErrorSuite extends CatsSuite { + implicit val eqThrow: Eq[Throwable] = Eq.fromUniversalEquals + val successful: Try[Int] = Success(42) val failedValue: Throwable = new IllegalArgumentException("default failure") val otherValue: Throwable = new IllegalStateException("other failure") @@ -37,19 +39,19 @@ class MonadErrorSuite extends CatsSuite { } test("ensureP returns the successful value if the partial function is not defined") { - successful.ensureP { + successful.reject { case i if i < 0 => failedValue } should === (successful) } test("ensureP returns the original failure, when applied to a failure") { - failed.ensureP { + failed.reject { case i if i < 0 => otherValue } should === (failed) } test("ensureP raises an error if the partial function is defined") { - successful.ensureP { + successful.reject { case i if i > 0 => failedValue } should === (failed) } From f91e7927af78cb398ca74e64f7446507446f71e9 Mon Sep 17 00:00:00 2001 From: Greg Wogan-Browne Date: Thu, 15 Mar 2018 11:12:57 -0700 Subject: [PATCH 3/3] Use applyOrElse to potentially avoid evaluating PF twice. --- core/src/main/scala/cats/syntax/monadError.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/syntax/monadError.scala b/core/src/main/scala/cats/syntax/monadError.scala index 4e34493255..19f2c58432 100644 --- a/core/src/main/scala/cats/syntax/monadError.scala +++ b/core/src/main/scala/cats/syntax/monadError.scala @@ -17,7 +17,9 @@ final class MonadErrorOps[F[_], E, A](val fa: F[A]) extends AnyVal { F.ensureOr(fa)(error)(predicate) def reject(pf: PartialFunction[A, E])(implicit F: MonadError[F, E]): F[A] = - F.flatMap(fa)(a => if (pf.isDefinedAt(a)) F.raiseError(pf(a)) else F.pure(a)) + F.flatMap(fa) { a => + pf.andThen(F.raiseError[A]).applyOrElse(a, (_: A) => fa) + } def adaptError(pf: PartialFunction[E, E])(implicit F: MonadError[F, E]): F[A] = F.adaptError(fa)(pf)