diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index b99ed8785f..bbb196d91b 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -3,6 +3,7 @@ package syntax trait AllSyntax extends ApplicativeSyntax + with ApplicativeErrorSyntax with ApplySyntax with BifunctorSyntax with BifoldableSyntax diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala new file mode 100644 index 0000000000..22e0927c5b --- /dev/null +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -0,0 +1,37 @@ +package cats +package syntax + +import cats.data.{Xor, XorT} + +trait ApplicativeErrorSyntax { + implicit def applicativeErrorIdSyntax[E](e: E): ApplicativeErrorIdOps[E] = + new ApplicativeErrorIdOps(e) + + implicit def applicativeErrorSyntax[F[_, _], E, A](fa: F[E, A])(implicit F: ApplicativeError[F[E, ?], E]): ApplicativeErrorOps[F[E, ?], E, A] = + new ApplicativeErrorOps[F[E, ?], E, A](fa) +} + +final class ApplicativeErrorIdOps[E](e: E) { + def raiseError[F[_], A](implicit F: ApplicativeError[F, E]): F[A] = + F.raiseError(e) +} + +final class ApplicativeErrorOps[F[_], E, A](fa: F[A])(implicit F: ApplicativeError[F, E]) { + def handleError(f: E => A): F[A] = + F.handleError(fa)(f) + + def handleErrorWith(f: E => F[A]): F[A] = + F.handleErrorWith(fa)(f) + + def attempt: F[E Xor A] = + F.attempt(fa) + + def attemptT: XorT[F, E, A] = + F.attemptT(fa) + + def recover(pf: PartialFunction[E, A]): F[A] = + F.recover(fa)(pf) + + def recoverWith(pf: PartialFunction[E, F[A]]): F[A] = + F.recoverWith(fa)(pf) +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 26ec7fd4f5..668c2f7311 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -3,6 +3,7 @@ package cats package object syntax { object all extends AllSyntax object applicative extends ApplicativeSyntax + object applicativeError extends ApplicativeErrorSyntax object apply extends ApplySyntax object bifunctor extends BifunctorSyntax object bifoldable extends BifoldableSyntax diff --git a/tests/src/test/scala/cats/tests/ApplicativeErrorTests.scala b/tests/src/test/scala/cats/tests/ApplicativeErrorTests.scala new file mode 100644 index 0000000000..e23e35a20d --- /dev/null +++ b/tests/src/test/scala/cats/tests/ApplicativeErrorTests.scala @@ -0,0 +1,41 @@ +package cats +package tests + +import cats.data.{Xor, XorT} + +class ApplicativeErrorCheck extends CatsSuite { + + type ErrorOr[A] = String Xor A + + val failed: String Xor Int = + "Badness".raiseError[ErrorOr, Int] + + test("raiseError syntax creates an Xor with the correct type parameters") { + failed should === ("Badness".left[Int]) + } + + test("handleError syntax transforms an error to a success") { + failed.handleError(error => error.length) should === (7.right) + } + + test("handleErrorWith transforms an error to a success") { + failed.handleErrorWith(error => error.length.right) should === (7.right) + } + + test("attempt syntax creates a wrapped Xor") { + failed.attempt should === ("Badness".left.right) + } + + test("attemptT syntax creates an XorT") { + failed.attemptT should === (XorT[ErrorOr, String, Int](failed.right)) + } + + test("recover syntax transforms an error to a success") { + failed.recover { case error => error.length } should === (7.right) + } + + test("recoverWith transforms an error to a success") { + failed.recoverWith { case error => error.length.right } should === (7.right) + } + +} \ No newline at end of file diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index f5533c86f9..3663d17917 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -214,4 +214,29 @@ class SyntaxTests extends AllInstances with AllSyntax { val la = mock[Eval[A]] val lfa = la.pureEval[F] } + + def testApplicativeError[F[_, _], E, A](implicit F: ApplicativeError[F[E, ?], E]): Unit = { + type G[X] = F[E, X] + + val e = mock[E] + val ga = e.raiseError[G, A] + + val gea = mock[G[A]] + + val ea = mock[E => A] + val gea1 = ga.handleError(ea) + + val egea = mock[E => G[A]] + val gea2 = ga.handleErrorWith(egea) + + val gxea = ga.attempt + + val gxtea = ga.attemptT + + val pfea = mock[PartialFunction[E, A]] + val gea3 = ga.recover(pfea) + + val pfegea = mock[PartialFunction[E, G[A]]] + val gea4 = ga.recoverWith(pfegea) + } }