From 5940e271674388e47f479531fb5ae1ad5f34020e Mon Sep 17 00:00:00 2001 From: Kailuo Wang Date: Tue, 18 Apr 2017 11:52:45 -0400 Subject: [PATCH 1/2] added leftT and improved existing creation API for EitherT --- core/src/main/scala/cats/data/EitherT.scala | 75 ++++++++++++++++++- .../test/scala/cats/tests/EitherTTests.scala | 20 ++--- 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 1b51488624..8dfca01dd4 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -89,7 +89,7 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def map[D](f: B => D)(implicit F: Functor[F]): EitherT[F, A, D] = bimap(identity, f) def semiflatMap[D](f: B => F[D])(implicit F: Monad[F]): EitherT[F, A, D] = - flatMap(b => EitherT.right[F, A, D](f(b))) + flatMap(b => EitherT.right(f(b))) def leftMap[C](f: A => C)(implicit F: Functor[F]): EitherT[F, C, B] = bimap(f, identity) @@ -236,11 +236,78 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { object EitherT extends EitherTInstances with EitherTFunctions private[data] trait EitherTFunctions { - final def left[F[_], A, B](fa: F[A])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fa)(Either.left)) - final def right[F[_], A, B](fb: F[B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fb)(Either.right)) + final class LeftPartiallyApplied[B] private[EitherTFunctions] { + def apply[F[_], A](fa: F[A])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fa)(Either.left)) + } + + /** + * Creates a left version of `EitherT[F, A, B]` from a `F[A]` + * {{{ + * scala> import cats.data.EitherT + * scala> import cats.implicits._ + * scala> EitherT.left[Int](Option("err")) + * res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Left(err))) + * }}} + */ + final def left[B]: LeftPartiallyApplied[B] = new LeftPartiallyApplied[B] + + final class LeftTPartiallyApplied[F[_], B] private[EitherTFunctions] { + def apply[A](a: A)(implicit F: Applicative[F]): EitherT[F, A, B] = EitherT(F.pure(Either.left(a))) + } + + /** + * Creates a left version of `EitherT[F, A, B]` from a `A` + * {{{ + * scala> import cats.data.EitherT + * scala> import cats.implicits._ + * scala> EitherT.leftT[Option, Int]("err") + * res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Left(err))) + * }}} + */ + final def leftT[F[_], B]: LeftTPartiallyApplied[F, B] = new LeftTPartiallyApplied[F, B] + + final class RightPartiallyApplied[A] private[EitherTFunctions] { + def apply[F[_], B](fb: F[B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fb)(Either.right)) + } + + /** + * Creates a right version of `EitherT[F, A, B]` from a `F[B]` + * {{{ + * scala> import cats.data.EitherT + * scala> import cats.implicits._ + * scala> EitherT.right[String](Option(3)) + * res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Right(3))) + * }}} + */ + final def right[A]: RightPartiallyApplied[A] = new RightPartiallyApplied[A] + + final class PurePartiallyApplied[F[_], A] private[EitherTFunctions] { + def apply[B](b: B)(implicit F: Applicative[F]): EitherT[F, A, B] = right(F.pure(b)) + } + + /** + * Creates a new `EitherT[F, A, B]` from a `B` + * {{{ + * scala> import cats.data.EitherT + * scala> import cats.implicits._ + * scala> EitherT.pure[Option, String](3) + * res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Right(3))) + * }}} + */ + final def pure[F[_], A]: PurePartiallyApplied[F, A] = new PurePartiallyApplied[F, A] + + /** + * Alias for [[pure]] + * {{{ + * scala> import cats.data.EitherT + * scala> import cats.implicits._ + * scala> EitherT.rightT[Option, String](3) + * res0: cats.data.EitherT[Option,String,Int] = EitherT(Some(Right(3))) + * }}} + */ + final def rightT[F[_], A]: PurePartiallyApplied[F, A] = pure - final def pure[F[_], A, B](b: B)(implicit F: Applicative[F]): EitherT[F, A, B] = right(F.pure(b)) /** * Alias for [[right]] diff --git a/tests/src/test/scala/cats/tests/EitherTTests.scala b/tests/src/test/scala/cats/tests/EitherTTests.scala index dbe79220ce..e97648853e 100644 --- a/tests/src/test/scala/cats/tests/EitherTTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTTests.scala @@ -193,33 +193,33 @@ class EitherTTests extends CatsSuite { } test("recover recovers handled values") { - val eithert = EitherT.left[Id, String, Int]("eithert") + val eithert = EitherT.leftT[Id, Int]("eithert") eithert.recover { case "eithert" => 5 }.isRight should === (true) } test("recover ignores unhandled values") { - val eithert = EitherT.left[Id, String, Int]("eithert") + val eithert = EitherT.leftT[Id, Int]("eithert") eithert.recover { case "noteithert" => 5 } should === (eithert) } test("recover ignores the right side") { - val eithert = EitherT.right[Id, String, Int](10) + val eithert = EitherT.pure[Id, String](10) eithert.recover { case "eithert" => 5 } should === (eithert) } test("recoverWith recovers handled values") { - val eithert = EitherT.left[Id, String, Int]("eithert") - eithert.recoverWith { case "eithert" => EitherT.right[Id, String, Int](5) }.isRight should === (true) + val eithert = EitherT.leftT[Id, Int]("eithert") + eithert.recoverWith { case "eithert" => EitherT.pure[Id, String](5) }.isRight should === (true) } test("recoverWith ignores unhandled values") { - val eithert = EitherT.left[Id, String, Int]("eithert") - eithert.recoverWith { case "noteithert" => EitherT.right[Id, String, Int](5) } should === (eithert) + val eithert = EitherT.leftT[Id, Int]("eithert") + eithert.recoverWith { case "noteithert" => EitherT.pure[Id, String](5) } should === (eithert) } test("recoverWith ignores the right side") { - val eithert = EitherT.right[Id, String, Int](10) - eithert.recoverWith { case "eithert" => EitherT.right[Id, String, Int](5) } should === (eithert) + val eithert = EitherT.pure[Id, String](10) + eithert.recoverWith { case "eithert" => EitherT.pure[Id, String](5) } should === (eithert) } test("transform consistent with value.map") { @@ -374,7 +374,7 @@ class EitherTTests extends CatsSuite { test("ensure should fail if predicate not satisfied") { forAll { (x: EitherT[Id, String, Int], s: String, p: Int => Boolean) => if (x.isRight && !p(x getOrElse 0)) { - x.ensure(s)(p) should === (EitherT.left[Id, String, Int](s)) + x.ensure(s)(p) should === (EitherT.leftT[Id, Int](s)) } } } From 5e67748a1ae286e2bad024c2a2497cec9347401f Mon Sep 17 00:00:00 2001 From: Kailuo Wang Date: Thu, 20 Apr 2017 11:39:39 -0400 Subject: [PATCH 2/2] added fromOptionF --- core/src/main/scala/cats/data/EitherT.scala | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 8dfca01dd4..419c1e6e55 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -362,6 +362,19 @@ private[data] trait EitherTFunctions { EitherT(F.pure(Either.fromOption(opt, ifNone))) } + /** Transforms an `F[Option]` into an `EitherT`, using the second argument if the `Option` is a `None`. + * {{{ + * scala> import cats.implicits._ + * scala> val o: Option[Int] = None + * scala> EitherT.fromOptionF(List(o), "Answer not known.") + * res0: EitherT[List, String, Int] = EitherT(List(Left(Answer not known.))) + * scala> EitherT.fromOptionF(List(Option(42)), "Answer not known.") + * res1: EitherT[List, String, Int] = EitherT(List(Right(42))) + * }}} + */ + final def fromOptionF[F[_], E, A](fopt: F[Option[A]], ifNone: => E)(implicit F: Functor[F]): EitherT[F, E, A] = + EitherT(F.map(fopt)(opt => Either.fromOption(opt, ifNone))) + /** If the condition is satisfied, return the given `A` in `Right` * lifted into the specified `Applicative`, otherwise, return the * given `E` in `Left` lifted into the specified `Applicative`.