From a75d0f9f6421f228babcf97a2ca6b3773e66852b Mon Sep 17 00:00:00 2001 From: Marco Di Paola Date: Thu, 30 May 2019 16:23:02 +0100 Subject: [PATCH 1/4] add attemptCase to ApplicativeErrorOps --- .../main/scala/cats/syntax/applicativeError.scala | 5 +++++ .../scala/cats/tests/ApplicativeErrorSuite.scala | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index 1d9c6ccbb5..60a7c64b1b 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -4,6 +4,8 @@ package syntax import cats.data.Validated.{Invalid, Valid} import cats.data.{EitherT, Validated} +import scala.reflect.ClassTag + trait ApplicativeErrorSyntax { implicit final def catsSyntaxApplicativeErrorId[E](e: E): ApplicativeErrorIdOps[E] = new ApplicativeErrorIdOps(e) @@ -83,6 +85,9 @@ final class ApplicativeErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal def attempt(implicit F: ApplicativeError[F, E]): F[Either[E, A]] = F.attempt(fa) + def attemptCase[EE](implicit F: ApplicativeError[F, E], tag: ClassTag[EE], ev: EE <:< E): F[Either[EE, A]] = + F.recover(F.map(fa)(Right[EE, A](_): Either[EE, A])) { case e: EE => Left[EE, A](e) } + def attemptT(implicit F: ApplicativeError[F, E]): EitherT[F, E, A] = F.attemptT(fa) diff --git a/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala b/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala index 658bd97e8d..2e08323eb5 100644 --- a/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala +++ b/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala @@ -23,6 +23,21 @@ class ApplicativeErrorSuite extends CatsSuite { failed.attempt should ===(Option(Left(()))) } + test("attemptCase[EE] syntax creates an F[Either[EE, A]]") { + trait Err + case class ErrA() extends Err + case class ErrB() extends Err + + implicit val eqForErr: Eq[Err] = Eq.fromUniversalEquals[Err] + implicit val eqForErrA: Eq[ErrA] = Eq.fromUniversalEquals[ErrA] + implicit val eqForErrB: Eq[ErrB] = Eq.fromUniversalEquals[ErrB] + + val failed: Either[Err, Int] = ErrA().raiseError[Either[Err, ?], Int] + + failed.attemptCase[ErrA] should ===(ErrA().asLeft[Int].asRight[Err]) + failed.attemptCase[ErrB] should ===(Either.left[Err, Either[ErrB, Int]](ErrA())) + } + test("attemptT syntax creates an EitherT") { failed.attemptT should ===(EitherT[Option, Unit, Int](Option(Left(())))) } From c1363bb63cdc6787f6c225d8ebc36f1f8ca535b1 Mon Sep 17 00:00:00 2001 From: Marco Di Paola Date: Fri, 21 Jun 2019 16:54:57 +0100 Subject: [PATCH 2/4] add attemptNarrow to ApplicativeErrorOps --- core/src/main/scala/cats/syntax/applicativeError.scala | 2 +- tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index 60a7c64b1b..54005b643d 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -85,7 +85,7 @@ final class ApplicativeErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal def attempt(implicit F: ApplicativeError[F, E]): F[Either[E, A]] = F.attempt(fa) - def attemptCase[EE](implicit F: ApplicativeError[F, E], tag: ClassTag[EE], ev: EE <:< E): F[Either[EE, A]] = + def attemptNarrow[EE](implicit F: ApplicativeError[F, E], tag: ClassTag[EE], ev: EE <:< E): F[Either[EE, A]] = F.recover(F.map(fa)(Right[EE, A](_): Either[EE, A])) { case e: EE => Left[EE, A](e) } def attemptT(implicit F: ApplicativeError[F, E]): EitherT[F, E, A] = diff --git a/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala b/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala index 2e08323eb5..3a181c58c6 100644 --- a/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala +++ b/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala @@ -23,7 +23,7 @@ class ApplicativeErrorSuite extends CatsSuite { failed.attempt should ===(Option(Left(()))) } - test("attemptCase[EE] syntax creates an F[Either[EE, A]]") { + test("attemptNarrow[EE] syntax creates an F[Either[EE, A]]") { trait Err case class ErrA() extends Err case class ErrB() extends Err @@ -34,8 +34,8 @@ class ApplicativeErrorSuite extends CatsSuite { val failed: Either[Err, Int] = ErrA().raiseError[Either[Err, ?], Int] - failed.attemptCase[ErrA] should ===(ErrA().asLeft[Int].asRight[Err]) - failed.attemptCase[ErrB] should ===(Either.left[Err, Either[ErrB, Int]](ErrA())) + failed.attemptNarrow[ErrA] should ===(ErrA().asLeft[Int].asRight[Err]) + failed.attemptNarrow[ErrB] should ===(Either.left[Err, Either[ErrB, Int]](ErrA())) } test("attemptT syntax creates an EitherT") { From 3767ca338077564188c1b2d87ae9218a47ee053f Mon Sep 17 00:00:00 2001 From: Marco Di Paola Date: Fri, 2 Aug 2019 22:57:44 +0100 Subject: [PATCH 3/4] add test for attemptNarrow and parametrized types --- .../cats/tests/ApplicativeErrorSuite.scala | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala b/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala index 3a181c58c6..a47e1413d4 100644 --- a/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala +++ b/tests/src/test/scala/cats/tests/ApplicativeErrorSuite.scala @@ -38,6 +38,28 @@ class ApplicativeErrorSuite extends CatsSuite { failed.attemptNarrow[ErrB] should ===(Either.left[Err, Either[ErrB, Int]](ErrA())) } + test("attemptNarrow works for parametrized types") { + trait T[A] + case object Str extends T[String] + case class Num(i: Int) extends T[Int] + + implicit def eqForT[A]: Eq[T[A]] = Eq.fromUniversalEquals[T[A]] + implicit val eqForStr: Eq[Str.type] = Eq.fromUniversalEquals[Str.type] + implicit val eqForNum: Eq[Num] = Eq.fromUniversalEquals[Num] + + val e: Either[T[Int], Unit] = Num(1).asLeft[Unit] + e.attemptNarrow[Num] should ===(e.asRight[T[Int]]) + assertTypeError("e.attemptNarrow[Str.type]") + + val e2: Either[T[String], Unit] = Str.asLeft[Unit] + e2.attemptNarrow[Str.type] should ===(e2.asRight[T[String]]) + assertTypeError("e2.attemptNarrow[Num]") + + val e3: Either[List[T[String]], Unit] = List(Str).asLeft[Unit] + e3.attemptNarrow[List[Str.type]] should ===(e3.asRight[List[T[String]]]) + assertTypeError("e3.attemptNarrow[List[Num]]") + } + test("attemptT syntax creates an EitherT") { failed.attemptT should ===(EitherT[Option, Unit, Int](Option(Left(())))) } From f6e92284c8151e7a8831106f7b60c0aefc66750a Mon Sep 17 00:00:00 2001 From: marcodippy Date: Wed, 23 Oct 2019 08:54:38 +0100 Subject: [PATCH 4/4] move attemptNarrow to ApplicativeError typeclass --- core/src/main/scala/cats/ApplicativeError.scala | 8 ++++++++ core/src/main/scala/cats/syntax/applicativeError.scala | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index 9e72bb9edc..37e391b62c 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -1,6 +1,8 @@ package cats import cats.data.EitherT + +import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} import scala.util.control.NonFatal @@ -73,6 +75,12 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { */ def attemptT[A](fa: F[A]): EitherT[F, E, A] = EitherT(attempt(fa)) + /** + * Similar to [[attempt]], but it only handles errors of type `EE`. + */ + def attemptNarrow[EE, A](fa: F[A])(implicit tag: ClassTag[EE], ev: EE <:< E): F[Either[EE, A]] = + recover(map(fa)(Right[EE, A](_): Either[EE, A])) { case e: EE => Left[EE, A](e) } + /** * Recover from certain errors by mapping them to an `A` value. * diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index 54005b643d..cbe7153e1d 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -86,7 +86,7 @@ final class ApplicativeErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal F.attempt(fa) def attemptNarrow[EE](implicit F: ApplicativeError[F, E], tag: ClassTag[EE], ev: EE <:< E): F[Either[EE, A]] = - F.recover(F.map(fa)(Right[EE, A](_): Either[EE, A])) { case e: EE => Left[EE, A](e) } + F.attemptNarrow[EE, A](fa) def attemptT(implicit F: ApplicativeError[F, E]): EitherT[F, E, A] = F.attemptT(fa)