From 8f49403eacc436ad5404faedace0b70b9e0d0a69 Mon Sep 17 00:00:00 2001 From: Ben Plommer Date: Sun, 2 Dec 2018 22:53:37 +0000 Subject: [PATCH 1/4] Add `rethrowT` method to EitherT, inverse of `MonadError#attemptT` --- core/src/main/scala/cats/data/EitherT.scala | 6 ++++++ .../src/test/scala/cats/tests/EitherTSuite.scala | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index a17e6ab7c9..eb7f3668a1 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -44,6 +44,12 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { case other => F.pure(other) }) + /** + * Inverse of `MonadError#attemptT` + */ + def rethrowT(implicit F: MonadError[F, A]): F[B] = + F.rethrow(value) + def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = fold(f, identity) def valueOrF[BB >: B](f: A => F[BB])(implicit F: Monad[F]): F[BB] = diff --git a/tests/src/test/scala/cats/tests/EitherTSuite.scala b/tests/src/test/scala/cats/tests/EitherTSuite.scala index e9d0389bc6..e5679b5afb 100644 --- a/tests/src/test/scala/cats/tests/EitherTSuite.scala +++ b/tests/src/test/scala/cats/tests/EitherTSuite.scala @@ -7,6 +7,7 @@ import cats.data.EitherT import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.kernel.laws.discipline.{EqTests, MonoidTests, OrderTests, PartialOrderTests, SemigroupTests} +import scala.util.{Failure, Success, Try} class EitherTSuite extends CatsSuite { implicit val iso = SemigroupalTests.Isomorphisms @@ -270,6 +271,20 @@ class EitherTSuite extends CatsSuite { eithert.recoverWith { case "noteithert" => EitherT.pure[Id, String](5) } should ===(eithert) } + test("rethrowT is inverse of attemptT when applied to a successful value") { + implicit val eqThrow: Eq[Throwable] = Eq.fromUniversalEquals + val success: Try[Int] = Success(42) + + success.attemptT.rethrowT should ===(success) + } + + test("rethrowT is inverse of attemptT when applied to a failed value") { + implicit val eqThrow: Eq[Throwable] = Eq.fromUniversalEquals + val failed: Try[Int] = Failure(new IllegalArgumentException("error")) + + failed.attemptT.rethrowT should ===(failed) + } + test("transform consistent with value.map") { forAll { (eithert: EitherT[List, String, Int], f: Either[String, Int] => Either[Long, Double]) => eithert.transform(f) should ===(EitherT(eithert.value.map(f))) From 2d1f022c6c07e540cba55c858a0ce82268381101 Mon Sep 17 00:00:00 2001 From: Ben Plommer Date: Sun, 2 Dec 2018 13:21:41 +0000 Subject: [PATCH 2/4] add tests for `MonadError#rethrow` and correct descriptions of tests for `MonadError#reject` --- .../src/test/scala/cats/tests/MonadErrorSuite.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/src/test/scala/cats/tests/MonadErrorSuite.scala b/tests/src/test/scala/cats/tests/MonadErrorSuite.scala index b989407b72..750d17f480 100644 --- a/tests/src/test/scala/cats/tests/MonadErrorSuite.scala +++ b/tests/src/test/scala/cats/tests/MonadErrorSuite.scala @@ -38,22 +38,29 @@ class MonadErrorSuite extends CatsSuite { failed.ensureOr(_ => otherValue)(_ => true) should ===(failed) } - test("ensureP returns the successful value if the partial function is not defined") { + test("reject returns the successful value if the partial function is not defined") { successful.reject { case i if i < 0 => failedValue } should ===(successful) } - test("ensureP returns the original failure, when applied to a failure") { + test("reject returns the original failure, when applied to a failure") { failed.reject { case i if i < 0 => otherValue } should ===(failed) } - test("ensureP raises an error if the partial function is defined") { + test("reject raises an error if the partial function is defined") { successful.reject { case i if i > 0 => failedValue } should ===(failed) } + test("rethrow returns the failure, when applied to a Left of a failure") { + failed.attempt.rethrow should ===(failed) + } + + test("rethrow returns the successful value, when applied to a Right of a successful value") { + successful.attempt.rethrow should ===(successful) + } } From 55702f4bdc2c227d3fda13c718662de1b4890480 Mon Sep 17 00:00:00 2001 From: Ben Plommer Date: Sun, 2 Dec 2018 22:48:57 +0000 Subject: [PATCH 3/4] document `MonadErrorOps.reject` and refactor to one line --- core/src/main/scala/cats/syntax/monadError.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/syntax/monadError.scala b/core/src/main/scala/cats/syntax/monadError.scala index b011762f0d..fb275bedb5 100644 --- a/core/src/main/scala/cats/syntax/monadError.scala +++ b/core/src/main/scala/cats/syntax/monadError.scala @@ -18,10 +18,12 @@ final class MonadErrorOps[F[_], E, A](private 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) + /** + * Turns a successful value into the error returned by a given partial function if it is + * in the partial function's domain. + */ def reject(pf: PartialFunction[A, E])(implicit F: MonadError[F, E]): F[A] = - F.flatMap(fa) { a => - pf.andThen(F.raiseError[A]).applyOrElse(a, (_: A) => fa) - } + F.flatMap(fa)(pf.andThen(F.raiseError[A]).applyOrElse(_, (_: A) => fa)) def adaptError(pf: PartialFunction[E, E])(implicit F: MonadError[F, E]): F[A] = F.adaptError(fa)(pf) From 4c8a4fe581a91c1250d8a8f6f4df7076c401594f Mon Sep 17 00:00:00 2001 From: Ben Plommer Date: Wed, 9 Jan 2019 12:20:26 +0000 Subject: [PATCH 4/4] Reverse refactoring of MonadErrorOps.reject --- 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 fb275bedb5..9301bb3714 100644 --- a/core/src/main/scala/cats/syntax/monadError.scala +++ b/core/src/main/scala/cats/syntax/monadError.scala @@ -23,7 +23,9 @@ final class MonadErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal { * in the partial function's domain. */ def reject(pf: PartialFunction[A, E])(implicit F: MonadError[F, E]): F[A] = - F.flatMap(fa)(pf.andThen(F.raiseError[A]).applyOrElse(_, (_: A) => fa)) + 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)