From 20b8a58f3aceb65fd9a59ae7cf6e73ecfb77bda2 Mon Sep 17 00:00:00 2001 From: Zach Abbott Date: Sun, 17 Jul 2016 20:20:19 -0500 Subject: [PATCH 01/17] Put liftT on the companion object of XorT --- core/src/main/scala/cats/data/XorT.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 57cbadb09e..1194552dbb 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -210,7 +210,11 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { def toNested: Nested[F, A Xor ?, B] = Nested[F, A Xor ?, B](value) } -object XorT extends XorTInstances with XorTFunctions +object XorT extends XorTInstances with XorTFunctions { + + def liftT[M[_]: Functor, E, A](ma: M[A]): XorT[M, E, A] = XorT(Functor[M].map(ma)(Xor.right)) + +} trait XorTFunctions { final def left[F[_], A, B](fa: F[A])(implicit F: Functor[F]): XorT[F, A, B] = XorT(F.map(fa)(Xor.left)) @@ -280,7 +284,7 @@ private[data] abstract class XorTInstances extends XorTInstances1 { type TC[M[_]] = Functor[M] def liftT[M[_]: Functor, A](ma: M[A]): XorT[M, E, A] = - XorT(Functor[M].map(ma)(Xor.right)) + XorT.liftT(ma) } implicit def catsMonoidForXorT[F[_], L, A](implicit F: Monoid[F[L Xor A]]): Monoid[XorT[F, L, A]] = From bbf98aaca9a0ff3f52d5f5ec7d50c2286c9aa83f Mon Sep 17 00:00:00 2001 From: Zach Abbott Date: Mon, 18 Jul 2016 08:01:08 -0500 Subject: [PATCH 02/17] Put liftT into the XorTfunctions trait and alias it for right --- core/src/main/scala/cats/data/XorT.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 1194552dbb..60c768fac8 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -210,11 +210,7 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { def toNested: Nested[F, A Xor ?, B] = Nested[F, A Xor ?, B](value) } -object XorT extends XorTInstances with XorTFunctions { - - def liftT[M[_]: Functor, E, A](ma: M[A]): XorT[M, E, A] = XorT(Functor[M].map(ma)(Xor.right)) - -} +object XorT extends XorTInstances with XorTFunctions trait XorTFunctions { final def left[F[_], A, B](fa: F[A])(implicit F: Functor[F]): XorT[F, A, B] = XorT(F.map(fa)(Xor.left)) @@ -223,6 +219,11 @@ trait XorTFunctions { final def pure[F[_], A, B](b: B)(implicit F: Applicative[F]): XorT[F, A, B] = right(F.pure(b)) + /** + * Alias for XorT.right + */ + final def liftT[F[_], A, B](fb: F[B])(implicit F: Functor[F]): XorT[F, A, B] = right(fb) + /** Transforms an `Xor` into an `XorT`, lifted into the specified `Applicative`. * * Note: The return type is a FromXorPartiallyApplied[F], which has an apply method From ff457d9788ea2b59a4c43d4074123dbe190cd909 Mon Sep 17 00:00:00 2001 From: Zach Abbott Date: Wed, 20 Jul 2016 17:22:39 -0500 Subject: [PATCH 03/17] Make the XorT.right a scaladoc link --- core/src/main/scala/cats/data/XorT.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 60c768fac8..b177404c45 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -220,9 +220,9 @@ trait XorTFunctions { final def pure[F[_], A, B](b: B)(implicit F: Applicative[F]): XorT[F, A, B] = right(F.pure(b)) /** - * Alias for XorT.right + * Alias for [[XorT.right]] */ - final def liftT[F[_], A, B](fb: F[B])(implicit F: Functor[F]): XorT[F, A, B] = right(fb) + final def liftT[F[_], A, B](fb: F[B]): XorT[F, A, B] = right(fb) /** Transforms an `Xor` into an `XorT`, lifted into the specified `Applicative`. * From 480b8f374b8309e07b063594abd109ea11a21b57 Mon Sep 17 00:00:00 2001 From: Zach Abbott Date: Thu, 21 Jul 2016 09:54:55 -0500 Subject: [PATCH 04/17] Fix missing implicit evidence needed, add doctest example --- core/src/main/scala/cats/data/XorT.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index b177404c45..c8a4060731 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -221,8 +221,18 @@ trait XorTFunctions { /** * Alias for [[XorT.right]] + * {{{ + * scala> import cats.data.XorT + * scala> import cats.instances.option._ + * scala> val o: Option[Int] = Some(3) + * scala> val n: Option[Int] = None + * scala> XorT.liftT(o) + * res0: cats.data.XorT[Option,Nothing,Int] = XorT(Some(Right(3))) + * scala> XorT.liftT(n) + * res1: cats.data.XorT[Option,Nothing,Int] = XorT(None) + * }}} */ - final def liftT[F[_], A, B](fb: F[B]): XorT[F, A, B] = right(fb) + final def liftT[F[_], A, B](fb: F[B])(implicit F: Functor[F]): XorT[F, A, B] = right(fb) /** Transforms an `Xor` into an `XorT`, lifted into the specified `Applicative`. * From 1e277cd89141fafad1a374afd0593478f3f09e31 Mon Sep 17 00:00:00 2001 From: Zach Abbott Date: Thu, 21 Jul 2016 10:08:29 -0500 Subject: [PATCH 05/17] Use the implicits import instead of piece-meal --- core/src/main/scala/cats/data/XorT.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index c8a4060731..0796f03163 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -223,7 +223,7 @@ trait XorTFunctions { * Alias for [[XorT.right]] * {{{ * scala> import cats.data.XorT - * scala> import cats.instances.option._ + * scala> import cats.implicits._ * scala> val o: Option[Int] = Some(3) * scala> val n: Option[Int] = None * scala> XorT.liftT(o) From 3bc775fcfe53609c384c877665be179560c8b1be Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 14 Aug 2016 18:15:47 -0700 Subject: [PATCH 06/17] Prefer Either over Xor, fixes #1192 This commit keeps Xor and XorT but moves most Cats functions from Xor to Either. Xor will be removed after 0.8.0. - Add methods from Xor onto enrichment class of Either - Copy XorT into an EitherT based around Either - Styling: Occurences of Either are styled as Either[A, B] instead of A Either B (as it was in Xor) --- .../main/scala/cats/ApplicativeError.scala | 16 +- core/src/main/scala/cats/Eval.scala | 7 +- core/src/main/scala/cats/FlatMapRec.scala | 6 +- core/src/main/scala/cats/Foldable.scala | 27 +- core/src/main/scala/cats/MonadCombine.scala | 2 +- core/src/main/scala/cats/Traverse.scala | 15 +- core/src/main/scala/cats/arrow/Choice.scala | 20 +- .../src/main/scala/cats/arrow/FunctionK.scala | 6 +- core/src/main/scala/cats/data/Coproduct.scala | 17 +- core/src/main/scala/cats/data/EitherT.scala | 459 ++++++++++++++++++ core/src/main/scala/cats/data/Ior.scala | 14 +- core/src/main/scala/cats/data/Kleisli.scala | 2 +- .../main/scala/cats/data/NonEmptyList.scala | 8 +- .../main/scala/cats/data/NonEmptyVector.scala | 8 +- core/src/main/scala/cats/data/OptionT.scala | 15 +- core/src/main/scala/cats/data/StateT.scala | 4 +- core/src/main/scala/cats/data/Validated.scala | 10 +- core/src/main/scala/cats/data/Xor.scala | 8 +- core/src/main/scala/cats/data/XorT.scala | 12 +- .../main/scala/cats/instances/either.scala | 29 +- .../main/scala/cats/instances/function.scala | 7 +- .../main/scala/cats/instances/future.scala | 12 +- core/src/main/scala/cats/instances/list.scala | 10 +- .../main/scala/cats/instances/option.scala | 7 +- core/src/main/scala/cats/instances/try.scala | 11 +- core/src/main/scala/cats/package.scala | 7 +- .../scala/cats/syntax/applicativeError.scala | 6 +- core/src/main/scala/cats/syntax/either.scala | 208 +++++++- core/src/main/scala/cats/syntax/flatMap.scala | 5 +- .../main/scala/cats/syntax/monadCombine.scala | 3 +- docs/src/main/tut/validated.md | 12 +- free/src/main/scala/cats/free/Free.scala | 23 +- free/src/test/scala/cats/free/FreeTests.scala | 2 +- .../test/scala/cats/free/InjectTests.scala | 6 +- .../cats/laws/ApplicativeErrorLaws.scala | 14 +- .../src/main/scala/cats/laws/ChoiceLaws.scala | 3 +- .../main/scala/cats/laws/FlatMapRecLaws.scala | 5 +- .../discipline/ApplicativeErrorTests.scala | 8 +- .../cats/laws/discipline/ChoiceTests.scala | 3 +- .../laws/discipline/MonadErrorTests.scala | 8 +- .../cats/tests/ApplicativeErrorTests.scala | 14 +- .../scala/cats/tests/CoproductTests.scala | 4 +- .../src/test/scala/cats/tests/IorTests.scala | 8 +- .../test/scala/cats/tests/KleisliTests.scala | 6 +- .../test/scala/cats/tests/ListWrapper.scala | 2 +- .../cats/tests/MonadRecInstancesTests.scala | 8 +- .../test/scala/cats/tests/OptionTTests.scala | 48 +- .../scala/cats/tests/ValidatedTests.scala | 10 +- .../test/scala/cats/tests/WriterTTests.scala | 8 +- .../src/test/scala/cats/tests/XorTTests.scala | 6 +- .../src/test/scala/cats/tests/XorTests.scala | 4 +- 51 files changed, 911 insertions(+), 252 deletions(-) create mode 100644 core/src/main/scala/cats/data/EitherT.scala diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index f575c7f703..c6c751b696 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -1,6 +1,6 @@ package cats -import cats.data.{Xor, XorT} +import cats.data.EitherT /** * An applicative that also allows you to raise and or handle an error value. @@ -35,21 +35,21 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { def handleError[A](fa: F[A])(f: E => A): F[A] = handleErrorWith(fa)(f andThen pure) /** - * Handle errors by turning them into [[cats.data.Xor.Left]] values. + * Handle errors by turning them into [[scala.util.Either]] values. * - * If there is no error, then an [[cats.data.Xor.Right]] value will be returned instead. + * If there is no error, then an [[scala.util.Right]] value will be returned instead. * * All non-fatal errors should be handled by this method. */ - def attempt[A](fa: F[A]): F[E Xor A] = handleErrorWith( - map(fa)(Xor.right[E, A]) - )(e => pure(Xor.left(e))) + def attempt[A](fa: F[A]): F[Either[E, A]] = handleErrorWith( + map(fa)(Right(_): Either[E, A]) + )(e => pure(Left(e))) /** - * Similar to [[attempt]], but wraps the result in a [[cats.data.XorT]] for + * Similar to [[attempt]], but wraps the result in a [[cats.data.EitherT]] for * convenience. */ - def attemptT[A](fa: F[A]): XorT[F, E, A] = XorT(attempt(fa)) + def attemptT[A](fa: F[A]): EitherT[F, E, A] = EitherT(attempt(fa)) /** * Recover from certain errors by mapping them to an `A` value. diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 82a3a1a267..1687ff83e8 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -1,7 +1,6 @@ package cats import scala.annotation.tailrec -import cats.data.Xor import cats.syntax.all._ /** @@ -302,10 +301,10 @@ private[cats] trait EvalInstances extends EvalInstances0 { def flatMap[A, B](fa: Eval[A])(f: A => Eval[B]): Eval[B] = fa.flatMap(f) def extract[A](la: Eval[A]): A = la.value def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Later(f(fa)) - def tailRecM[A, B](a: A)(f: A => Eval[A Xor B]): Eval[B] = + def tailRecM[A, B](a: A)(f: A => Eval[Either[A, B]]): Eval[B] = f(a).flatMap(_ match { - case Xor.Left(a1) => tailRecM(a1)(f) // recursion OK here, since flatMap is lazy - case Xor.Right(b) => Eval.now(b) + case Left(a1) => tailRecM(a1)(f) // recursion OK here, since flatMap is lazy + case Right(b) => Eval.now(b) }) } diff --git a/core/src/main/scala/cats/FlatMapRec.scala b/core/src/main/scala/cats/FlatMapRec.scala index 5658b3fe00..5bc95a8beb 100644 --- a/core/src/main/scala/cats/FlatMapRec.scala +++ b/core/src/main/scala/cats/FlatMapRec.scala @@ -2,8 +2,6 @@ package cats import simulacrum.typeclass -import cats.data.Xor - /** * Version of [[cats.FlatMap]] capable of stack-safe recursive `flatMap`s. * @@ -13,12 +11,12 @@ import cats.data.Xor @typeclass trait FlatMapRec[F[_]] extends FlatMap[F] { /** - * Keeps calling `f` until a `[[cats.data.Xor.Right Right]][B]` is returned. + * Keeps calling `f` until a `[[scala.util.Right]][B]` is returned. * * Implementations of this method must use constant stack space. * * `f` must use constant stack space. (It is OK to use a constant number of * `map`s and `flatMap`s inside `f`.) */ - def tailRecM[A, B](a: A)(f: A => F[A Xor B]): F[B] + def tailRecM[A, B](a: A)(f: A => F[Either[A, B]]): F[B] } diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 00c63a38da..acb89624dd 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -186,9 +186,8 @@ import simulacrum.typeclass * For example: * * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ - * scala> def parseInt(s: String): Option[Int] = Xor.catchOnly[NumberFormatException](s.toInt).toOption + * scala> def parseInt(s: String): Option[Int] = Either.catchOnly[NumberFormatException](s.toInt).toOption * scala> val F = Foldable[List] * scala> F.traverse_(List("333", "444"))(parseInt) * res0: Option[Unit] = Some(()) @@ -208,19 +207,18 @@ import simulacrum.typeclass /** * Behaves like traverse_, but uses [[Unapply]] to find the * [[Applicative]] instance for `G` - used when `G` is a - * type constructor with two or more parameters such as [[cats.data.Xor]] + * type constructor with two or more parameters such as [[scala.util.Either]] * * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ - * scala> def parseInt(s: String): Xor[String, Int] = - * | try { Xor.Right(s.toInt) } - * | catch { case _: NumberFormatException => Xor.Left("boo") } + * scala> def parseInt(s: String): Either[String, Int] = + * | try { Right(s.toInt) } + * | catch { case _: NumberFormatException => Left("boo") } * scala> val F = Foldable[List] * scala> F.traverseU_(List("333", "444"))(parseInt) - * res0: Xor[String, Unit] = Right(()) + * res0: Either[String, Unit] = Right(()) * scala> F.traverseU_(List("333", "zzz"))(parseInt) - * res1: Xor[String, Unit] = Left(boo) + * res1: Either[String, Unit] = Left(boo) * }}} * * Note that using `traverse_` instead of `traverseU_` would not compile without @@ -253,16 +251,15 @@ import simulacrum.typeclass /** * Behaves like sequence_, but uses [[Unapply]] to find the * [[Applicative]] instance for `G` - used when `G` is a - * type constructor with two or more parameters such as [[cats.data.Xor]] + * type constructor with two or more parameters such as [[scala.util.Either]] * * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ * scala> val F = Foldable[List] - * scala> F.sequenceU_(List(Xor.right[String, Int](333), Xor.Right(444))) - * res0: Xor[String, Unit] = Right(()) - * scala> F.sequenceU_(List(Xor.right[String, Int](333), Xor.Left("boo"))) - * res1: Xor[String, Unit] = Left(boo) + * scala> F.sequenceU_(List(Either.right[String, Int](333), Right(444))) + * res0: Either[String, Unit] = Right(()) + * scala> F.sequenceU_(List(Either.right[String, Int](333), Left("boo"))) + * res1: Either[String, Unit] = Left(boo) * }}} * * Note that using `sequence_` instead of `sequenceU_` would not compile without diff --git a/core/src/main/scala/cats/MonadCombine.scala b/core/src/main/scala/cats/MonadCombine.scala index 55cb76184e..a418cba774 100644 --- a/core/src/main/scala/cats/MonadCombine.scala +++ b/core/src/main/scala/cats/MonadCombine.scala @@ -11,7 +11,7 @@ import simulacrum.typeclass * Fold over the inner structure to combine all of the values with * our combine method inherited from MonoidK. The result is for us * to accumulate all of the "interesting" values of the inner G, so - * if G is Option, we collect all the Some values, if G is Xor, + * if G is Option, we collect all the Some values, if G is Either, * we collect all the Right values, etc. */ def unite[G[_], A](fga: F[G[A]])(implicit G: Foldable[G]): F[A] = diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index 9aef6df102..7e34eef82b 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -22,9 +22,8 @@ import simulacrum.typeclass * * Example: * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ - * scala> def parseInt(s: String): Option[Int] = Xor.catchOnly[NumberFormatException](s.toInt).toOption + * scala> def parseInt(s: String): Option[Int] = Either.catchOnly[NumberFormatException](s.toInt).toOption * scala> List("1", "2", "3").traverse(parseInt) * res0: Option[List[Int]] = Some(List(1, 2, 3)) * scala> List("1", "two", "3").traverse(parseInt) @@ -39,14 +38,13 @@ import simulacrum.typeclass * * Example: * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ - * scala> def parseInt(s: String): Xor[String, Int] = Xor.catchOnly[NumberFormatException](s.toInt).leftMap(_ => "no number") + * scala> def parseInt(s: String): Either[String, Int] = Either.catchOnly[NumberFormatException](s.toInt).leftMap(_ => "no number") * scala> val ns = List("1", "2", "3") * scala> ns.traverseU(parseInt) - * res0: Xor[String, List[Int]] = Right(List(1, 2, 3)) - * scala> ns.traverse[Xor[String, ?], Int](parseInt) - * res1: Xor[String, List[Int]] = Right(List(1, 2, 3)) + * res0: Either[String, List[Int]] = Right(List(1, 2, 3)) + * scala> ns.traverse[Either[String, ?], Int](parseInt) + * res1: Either[String, List[Int]] = Right(List(1, 2, 3)) * }}} */ def traverseU[A, GB](fa: F[A])(f: A => GB)(implicit U: Unapply[Applicative, GB]): U.M[F[U.A]] = @@ -57,9 +55,8 @@ import simulacrum.typeclass * * Example: * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ - * scala> def parseInt(s: String): Option[Int] = Xor.catchOnly[NumberFormatException](s.toInt).toOption + * scala> def parseInt(s: String): Option[Int] = Either.catchOnly[NumberFormatException](s.toInt).toOption * scala> val x = Option(List("1", "two", "3")) * scala> x.traverseM(_.map(parseInt)) * res0: List[Option[Int]] = List(Some(1), None, Some(3)) diff --git a/core/src/main/scala/cats/arrow/Choice.scala b/core/src/main/scala/cats/arrow/Choice.scala index df529e00cc..5954f1ede0 100644 --- a/core/src/main/scala/cats/arrow/Choice.scala +++ b/core/src/main/scala/cats/arrow/Choice.scala @@ -1,8 +1,6 @@ package cats package arrow -import cats.data.Xor - trait Choice[F[_, _]] extends Category[F] { /** @@ -12,20 +10,19 @@ trait Choice[F[_, _]] extends Category[F] { * * Example: * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ * scala> val b: Boolean => String = _ + " is a boolean" * scala> val i: Int => String = _ + " is an integer" - * scala> val f: (Boolean Xor Int) => String = Choice[Function1].choice(b, i) + * scala> val f: (Either[Boolean, Int]) => String = Choice[Function1].choice(b, i) * - * scala> f(Xor.right(3)) + * scala> f(Right(3)) * res0: String = 3 is an integer * - * scala> f(Xor.left(false)) + * scala> f(Left(false)) * res0: String = false is a boolean * }}} */ - def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Xor[A, B], C] + def choice[A, B, C](f: F[A, C], g: F[B, C]): F[Either[A, B], C] /** * An `F` that, given a source `A` on either the right or left side, will @@ -33,18 +30,17 @@ trait Choice[F[_, _]] extends Category[F] { * * Example: * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ - * scala> val f: (Int Xor Int) => Int = Choice[Function1].codiagonal[Int] + * scala> val f: (Either[Int, Int]) => Int = Choice[Function1].codiagonal[Int] * - * scala> f(Xor.right(3)) + * scala> f(Right(3)) * res0: Int = 3 * - * scala> f(Xor.left(3)) + * scala> f(Left(3)) * res1: Int = 3 * }}} */ - def codiagonal[A]: F[Xor[A, A], A] = choice(id, id) + def codiagonal[A]: F[Either[A, A], A] = choice(id, id) } object Choice { diff --git a/core/src/main/scala/cats/arrow/FunctionK.scala b/core/src/main/scala/cats/arrow/FunctionK.scala index eaadb4d598..4cf392d3ff 100644 --- a/core/src/main/scala/cats/arrow/FunctionK.scala +++ b/core/src/main/scala/cats/arrow/FunctionK.scala @@ -1,7 +1,7 @@ package cats package arrow -import cats.data.{Xor, Coproduct} +import cats.data. Coproduct trait FunctionK[F[_], G[_]] extends Serializable { self => def apply[A](fa: F[A]): G[A] @@ -17,8 +17,8 @@ trait FunctionK[F[_], G[_]] extends Serializable { self => def or[H[_]](h: FunctionK[H, G]): FunctionK[Coproduct[F, H, ?], G] = new FunctionK[Coproduct[F, H, ?], G] { def apply[A](fa: Coproduct[F, H, A]): G[A] = fa.run match { - case Xor.Left(ff) => self(ff) - case Xor.Right(gg) => h(gg) + case Left(ff) => self(ff) + case Right(gg) => h(gg) } } } diff --git a/core/src/main/scala/cats/data/Coproduct.scala b/core/src/main/scala/cats/data/Coproduct.scala index 35b4f8a638..cc552b6c0d 100644 --- a/core/src/main/scala/cats/data/Coproduct.scala +++ b/core/src/main/scala/cats/data/Coproduct.scala @@ -3,12 +3,13 @@ package data import cats.arrow.FunctionK import cats.functor.Contravariant +import cats.syntax.either._ -/** `F` on the left and `G` on the right of [[Xor]]. +/** `F` on the left and `G` on the right of [[scala.util.Either]]. * - * @param run The underlying [[Xor]]. + * @param run The underlying [[scala.util.Either]]. */ -final case class Coproduct[F[_], G[_], A](run: F[A] Xor G[A]) { +final case class Coproduct[F[_], G[_], A](run: Either[F[A], G[A]]) { import Coproduct._ @@ -86,17 +87,17 @@ final case class Coproduct[F[_], G[_], A](run: F[A] Xor G[A]) { object Coproduct extends CoproductInstances { def leftc[F[_], G[_], A](x: F[A]): Coproduct[F, G, A] = - Coproduct(Xor.left(x)) + Coproduct(Left(x)) def rightc[F[_], G[_], A](x: G[A]): Coproduct[F, G, A] = - Coproduct(Xor.right(x)) + Coproduct(Right(x)) final class CoproductLeft[G[_]] private[Coproduct] { - def apply[F[_], A](fa: F[A]): Coproduct[F, G, A] = Coproduct(Xor.left(fa)) + def apply[F[_], A](fa: F[A]): Coproduct[F, G, A] = Coproduct(Left(fa)) } final class CoproductRight[F[_]] private[Coproduct] { - def apply[G[_], A](ga: G[A]): Coproduct[F, G, A] = Coproduct(Xor.right(ga)) + def apply[G[_], A](ga: G[A]): Coproduct[F, G, A] = Coproduct(Right(ga)) } def left[G[_]]: CoproductLeft[G] = new CoproductLeft[G] @@ -106,7 +107,7 @@ object Coproduct extends CoproductInstances { private[data] sealed abstract class CoproductInstances3 { - implicit def catsDataEqForCoproduct[F[_], G[_], A](implicit E: Eq[F[A] Xor G[A]]): Eq[Coproduct[F, G, A]] = + implicit def catsDataEqForCoproduct[F[_], G[_], A](implicit E: Eq[Either[F[A], G[A]]]): Eq[Coproduct[F, G, A]] = Eq.by(_.run) implicit def catsDataFunctorForCoproduct[F[_], G[_]](implicit F0: Functor[F], G0: Functor[G]): Functor[Coproduct[F, G, ?]] = diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala new file mode 100644 index 0000000000..d7329b1fa7 --- /dev/null +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -0,0 +1,459 @@ +package cats +package data + +import cats.functor.Bifunctor +import cats.instances.either._ +import cats.syntax.either._ + +/** + * Transformer for `Either`, allowing the effect of an arbitrary type constructor `F` to be combined with the + * fail-fast effect of `Either`. + * + * `EitherT[F, A, B]` wraps a value of type `F[Either[A, B]]`. An `F[C]` can be lifted in to `EitherT[F, A, C]` via `EitherT.right`, + * and lifted in to a `EitherT[F, C, B]` via `EitherT.left`. + */ +final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { + + def fold[C](fa: A => C, fb: B => C)(implicit F: Functor[F]): F[C] = F.map(value)(_.fold(fa, fb)) + + def isLeft(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.isLeft) + + def isRight(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.isRight) + + def swap(implicit F: Functor[F]): EitherT[F, B, A] = EitherT(F.map(value)(_.swap)) + + def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = F.map(value)(_.getOrElse(default)) + + def getOrElseF[BB >: B](default: => F[BB])(implicit F: Monad[F]): F[BB] = { + F.flatMap(value) { + case Left(_) => default + case Right(b) => F.pure(b) + } + } + + def orElse[AA, BB >: B](default: => EitherT[F, AA, BB])(implicit F: Monad[F]): EitherT[F, AA, BB] = { + EitherT(F.flatMap(value) { + case Left(_) => default.value + case r @ Right(_) => F.pure(r.asInstanceOf[Either[AA, BB]]) + }) + } + + def recover(pf: PartialFunction[A, B])(implicit F: Functor[F]): EitherT[F, A, B] = + EitherT(F.map(value)(_.recover(pf))) + + def recoverWith(pf: PartialFunction[A, EitherT[F, A, B]])(implicit F: Monad[F]): EitherT[F, A, B] = + EitherT(F.flatMap(value) { + case Left(a) if pf.isDefinedAt(a) => pf(a).value + case other => F.pure(other) + }) + + def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = fold(f, identity) + + def forall(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.forall(f)) + + def exists(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.exists(f)) + + def ensure[AA >: A](onFailure: => AA)(f: B => Boolean)(implicit F: Functor[F]): EitherT[F, AA, B] = EitherT(F.map(value)(_.ensure(onFailure)(f))) + + def toOption(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.toOption)) + + def to[G[_]](implicit F: Functor[F], G: Alternative[G]): F[G[B]] = + F.map(value)(_.to[G, B]) + + def collectRight(implicit F: MonadCombine[F]): F[B] = + F.flatMap(value)(_.to[F, B]) + + def bimap[C, D](fa: A => C, fb: B => D)(implicit F: Functor[F]): EitherT[F, C, D] = EitherT(F.map(value)(_.bimap(fa, fb))) + + def bitraverse[G[_], C, D](f: A => G[C], g: B => G[D])(implicit traverseF: Traverse[F], applicativeG: Applicative[G]): G[EitherT[F, C, D]] = + applicativeG.map(traverseF.traverse(value)(axb => Bitraverse[Either].bitraverse(axb)(f, g)))(EitherT.apply) + + def applyAlt[D](ff: EitherT[F, A, B => D])(implicit F: Apply[F]): EitherT[F, A, D] = + EitherT[F, A, D](F.map2(this.value, ff.value)((xb, xbd) => Apply[Either[A, ?]].ap(xbd)(xb))) + + def flatMap[AA >: A, D](f: B => EitherT[F, AA, D])(implicit F: Monad[F]): EitherT[F, AA, D] = + EitherT(F.flatMap(value) { + case l @ Left(_) => F.pure(l.asInstanceOf[Either[AA, D]]) + case Right(b) => f(b).value + }) + + def flatMapF[AA >: A, D](f: B => F[Either[AA, D]])(implicit F: Monad[F]): EitherT[F, AA, D] = + flatMap(f andThen EitherT.apply) + + def transform[C, D](f: Either[A, B] => Either[C, D])(implicit F: Functor[F]): EitherT[F, C, D] = + EitherT(F.map(value)(f)) + + def subflatMap[AA >: A, D](f: B => Either[AA, D])(implicit F: Functor[F]): EitherT[F, AA, D] = + transform(_.flatMap(f)) + + 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))) + + def leftMap[C](f: A => C)(implicit F: Functor[F]): EitherT[F, C, B] = bimap(f, identity) + + def compare(that: EitherT[F, A, B])(implicit o: Order[F[Either[A, B]]]): Int = + o.compare(value, that.value) + + def partialCompare(that: EitherT[F, A, B])(implicit p: PartialOrder[F[Either[A, B]]]): Double = + p.partialCompare(value, that.value) + + def ===(that: EitherT[F, A, B])(implicit eq: Eq[F[Either[A, B]]]): Boolean = + eq.eqv(value, that.value) + + def traverse[G[_], D](f: B => G[D])(implicit traverseF: Traverse[F], applicativeG: Applicative[G]): G[EitherT[F, A, D]] = + applicativeG.map(traverseF.traverse(value)(axb => Traverse[Either[A, ?]].traverse(axb)(f)))(EitherT.apply) + + def foldLeft[C](c: C)(f: (C, B) => C)(implicit F: Foldable[F]): C = + F.foldLeft(value, c)((c, axb) => axb.foldLeft(c)(f)) + + def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C])(implicit F: Foldable[F]): Eval[C] = + F.foldRight(value, lc)((axb, lc) => axb.foldRight(lc)(f)) + + def merge[AA >: A](implicit ev: B <:< AA, F: Functor[F]): F[AA] = F.map(value)(_.fold(identity, ev.apply)) + + /** + * Similar to [[Either.combine]] but mapped over an `F` context. + * + * Examples: + * {{{ + * scala> import cats.data.EitherT + * scala> import cats.implicits._ + * scala> val l1: EitherT[Option, String, Int] = EitherT.left(Some("error 1")) + * scala> val l2: EitherT[Option, String, Int] = EitherT.left(Some("error 2")) + * scala> val r3: EitherT[Option, String, Int] = EitherT.right(Some(3)) + * scala> val r4: EitherT[Option, String, Int] = EitherT.right(Some(4)) + * scala> val noneEitherT: EitherT[Option, String, Int] = EitherT.left(None) + * + * scala> l1 combine l2 + * res0: EitherT[Option, String, Int] = EitherT(Some(Left(error 1))) + * + * scala> l1 combine r3 + * res1: EitherT[Option, String, Int] = EitherT(Some(Left(error 1))) + * + * scala> r3 combine l1 + * res2: EitherT[Option, String, Int] = EitherT(Some(Left(error 1))) + * + * scala> r3 combine r4 + * res3: EitherT[Option, String, Int] = EitherT(Some(Right(7))) + * + * scala> l1 combine noneEitherT + * res4: EitherT[Option, String, Int] = EitherT(None) + * + * scala> noneEitherT combine l1 + * res5: EitherT[Option, String, Int] = EitherT(None) + * + * scala> r3 combine noneEitherT + * res6: EitherT[Option, String, Int] = EitherT(None) + * + * scala> noneEitherT combine r4 + * res7: EitherT[Option, String, Int] = EitherT(None) + * }}} + */ + def combine(that: EitherT[F, A, B])(implicit F: Apply[F], B: Semigroup[B]): EitherT[F, A, B] = + EitherT(F.map2(this.value, that.value)(_ combine _)) + + def toValidated(implicit F: Functor[F]): F[Validated[A, B]] = + F.map(value)(_.toValidated) + + def toValidatedNel(implicit F: Functor[F]): F[ValidatedNel[A, B]] = + F.map(value)(_.toValidatedNel) + + /** Run this value as a `[[Validated]]` against the function and convert it back to an `[[EitherT]]`. + * + * The [[Applicative]] instance for `EitherT` "fails fast" - it is often useful to "momentarily" have + * it accumulate errors instead, which is what the `[[Validated]]` data type gives us. + * + * Example: + * {{{ + * scala> import cats.implicits._ + * scala> type Error = String + * scala> val v1: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList.of("error 1")) + * scala> val v2: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList.of("error 2")) + * scala> val xort: EitherT[Option, Error, Int] = EitherT(Some(Either.left("error 3"))) + * scala> xort.withValidated { v3 => (v1 |@| v2 |@| v3.leftMap(NonEmptyList.of(_))).map{ case (i, j, k) => i + j + k } } + * res0: EitherT[Option, NonEmptyList[Error], Int] = EitherT(Some(Left(NonEmptyList(error 1, error 2, error 3)))) + * }}} + */ + def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB])(implicit F: Functor[F]): EitherT[F, AA, BB] = + EitherT(F.map(value)(xor => f(xor.toValidated).toEither)) + + def show(implicit show: Show[F[Either[A, B]]]): String = show.show(value) + + /** + * Transform this `EitherT[F, A, B]` into a `[[Nested]][F, Either[A, ?], B]`. + * + * An example where `toNested` can be used, is to get the `Apply.ap` function with the + * behavior from the composed `Apply` instances from `F` and `Either[A, ?]`, which is + * inconsistent with the behavior of the `ap` from `Monad` of `EitherT`. + * + * {{{ + * scala> import cats.data.{Nested, EitherT} + * scala> import cats.implicits._ + * scala> val ff: EitherT[List, String, Int => String] = + * | EitherT(List(Either.right(_.toString), Either.left("error"))) + * scala> val fa: EitherT[List, String, Int] = + * | EitherT(List(Either.right(1), Either.right(2))) + * scala> type ErrorOr[A] = Either[String, A] + * scala> type ListErrorOr[A] = Nested[List, ErrorOr, A] + * scala> ff.ap(fa) + * res0: EitherT[List,String,String] = EitherT(List(Right(1), Right(2), Left(error))) + * scala> EitherT((ff.toNested: ListErrorOr[Int => String]).ap(fa.toNested: ListErrorOr[Int]).value) + * res1: EitherT[List,String,String] = EitherT(List(Right(1), Right(2), Left(error), Left(error))) + * }}} + * + * Note that we need the `ErrorOr` type alias above because otherwise we can't use the + * syntax function `ap` on `Nested[List, Either[A, ?], B]`. This won't be needed after cats has + * decided [[https://github.com/typelevel/cats/issues/1073 how to handle the SI-2712 fix]]. + */ + def toNested: Nested[F, Either[A, ?], B] = Nested[F, Either[A, ?], B](value) +} + +object EitherT extends EitherTInstances with EitherTFunctions + +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 def pure[F[_], A, B](b: B)(implicit F: Applicative[F]): EitherT[F, A, B] = right(F.pure(b)) + + /** Transforms an `Either` into an `EitherT`, lifted into the specified `Applicative`. + * + * Note: The return type is a FromEitherPartiallyApplied[F], which has an apply method + * on it, allowing you to call fromEither like this: + * {{{ + * scala> import cats.implicits._ + * scala> val t: Either[String, Int] = Either.right(3) + * scala> EitherT.fromEither[Option](t) + * res0: EitherT[Option, String, Int] = EitherT(Some(Right(3))) + * }}} + * + * The reason for the indirection is to emulate currying type parameters. + */ + final def fromEither[F[_]]: FromEitherPartiallyApplied[F] = new FromEitherPartiallyApplied + + final class FromEitherPartiallyApplied[F[_]] private[EitherTFunctions] { + def apply[E, A](xor: Either[E, A])(implicit F: Applicative[F]): EitherT[F, E, A] = + EitherT(F.pure(xor)) + } +} + +private[data] abstract class EitherTInstances extends EitherTInstances1 { + + /* TODO violates right absorbtion, right distributivity, and left distributivity -- re-enable when MonadCombine laws are split in to weak/strong + implicit def catsDataMonadCombineForEitherT[F[_], L](implicit F: Monad[F], L: Monoid[L]): MonadCombine[EitherT[F, L, ?]] = { + implicit val F0 = F + implicit val L0 = L + new EitherTMonadCombine[F, L] { implicit val F = F0; implicit val L = L0 } + } + */ + + implicit def catsDataOrderForEitherT[F[_], L, R](implicit F: Order[F[Either[L, R]]]): Order[EitherT[F, L, R]] = + new EitherTOrder[F, L, R] { + val F0: Order[F[Either[L, R]]] = F + } + + implicit def catsDataShowForEitherT[F[_], L, R](implicit sh: Show[F[Either[L, R]]]): Show[EitherT[F, L, R]] = + functor.Contravariant[Show].contramap(sh)(_.value) + + implicit def catsDataBifunctorForEitherT[F[_]](implicit F: Functor[F]): Bifunctor[EitherT[F, ?, ?]] = + new Bifunctor[EitherT[F, ?, ?]] { + override def bimap[A, B, C, D](fab: EitherT[F, A, B])(f: A => C, g: B => D): EitherT[F, C, D] = fab.bimap(f, g) + } + + implicit def catsDataTraverseForEitherT[F[_], L](implicit F: Traverse[F]): Traverse[EitherT[F, L, ?]] = + new EitherTTraverse[F, L] { + val F0: Traverse[F] = F + } + + implicit def catsDataTransLiftForEitherT[E]: TransLift.Aux[EitherT[?[_], E, ?], Functor] = + new TransLift[EitherT[?[_], E, ?]] { + type TC[M[_]] = Functor[M] + + def liftT[M[_]: Functor, A](ma: M[A]): EitherT[M, E, A] = + EitherT(Functor[M].map(ma)(Either.right)) + } + + implicit def catsMonoidForEitherT[F[_], L, A](implicit F: Monoid[F[Either[L, A]]]): Monoid[EitherT[F, L, A]] = + new EitherTMonoid[F, L, A] { implicit val F0 = F } + +} + +private[data] abstract class EitherTInstances1 extends EitherTInstances2 { + /* TODO violates monadFilter right empty law -- re-enable when MonadFilter laws are split in to weak/strong + implicit def catsDataMonadFilterForEitherT[F[_], L](implicit F: Monad[F], L: Monoid[L]): MonadFilter[EitherT[F, L, ?]] = { + implicit val F0 = F + implicit val L0 = L + new EitherTMonadFilter[F, L] { implicit val F = F0; implicit val L = L0 } + } + */ + + implicit def catsSemigroupForEitherT[F[_], L, A](implicit F: Semigroup[F[Either[L, A]]]): Semigroup[EitherT[F, L, A]] = + new EitherTSemigroup[F, L, A] { implicit val F0 = F } + + implicit def catsDataFoldableForEitherT[F[_], L](implicit F: Foldable[F]): Foldable[EitherT[F, L, ?]] = + new EitherTFoldable[F, L] { + val F0: Foldable[F] = F + } + + implicit def catsDataPartialOrderForEitherT[F[_], L, R](implicit F: PartialOrder[F[Either[L, R]]]): PartialOrder[EitherT[F, L, R]] = + new EitherTPartialOrder[F, L, R] { + val F0: PartialOrder[F[Either[L, R]]] = F + } + + implicit def catsDataBitraverseForEitherT[F[_]](implicit F: Traverse[F]): Bitraverse[EitherT[F, ?, ?]] = + new EitherTBitraverse[F] { + val F0: Traverse[F] = F + } +} + +private[data] abstract class EitherTInstances2 extends EitherTInstances3 { + implicit def catsDataMonadRecForEitherT[F[_], L](implicit F0: MonadRec[F]): MonadRec[EitherT[F, L, ?]] = + new EitherTMonadRec[F, L] { implicit val F = F0 } +} + +private[data] abstract class EitherTInstances3 extends EitherTInstances4 { + implicit def catsDataMonadErrorForEitherT[F[_], L](implicit F0: Monad[F]): MonadError[EitherT[F, L, ?], L] = + new EitherTMonadError[F, L] { implicit val F = F0 } + + implicit def catsDataSemigroupKForEitherT[F[_], L](implicit F0: Monad[F]): SemigroupK[EitherT[F, L, ?]] = + new EitherTSemigroupK[F, L] { implicit val F = F0 } + + implicit def catsDataEqForEitherT[F[_], L, R](implicit F: Eq[F[Either[L, R]]]): Eq[EitherT[F, L, R]] = + new EitherTEq[F, L, R] { + val F0: Eq[F[Either[L, R]]] = F + } +} + +private[data] abstract class EitherTInstances4 { + implicit def catsDataFunctorForEitherT[F[_], L](implicit F0: Functor[F]): Functor[EitherT[F, L, ?]] = + new EitherTFunctor[F, L] { implicit val F = F0 } +} + +private[data] trait EitherTSemigroup[F[_], L, A] extends Semigroup[EitherT[F, L, A]] { + implicit val F0: Semigroup[F[Either[L, A]]] + def combine(x: EitherT[F, L , A], y: EitherT[F, L , A]): EitherT[F, L , A] = + EitherT(F0.combine(x.value, y.value)) +} + +private[data] trait EitherTMonoid[F[_], L, A] extends Monoid[EitherT[F, L, A]] with EitherTSemigroup[F, L, A] { + implicit val F0: Monoid[F[Either[L, A]]] + def empty: EitherT[F, L, A] = EitherT(F0.empty) +} + +private[data] trait EitherTSemigroupK[F[_], L] extends SemigroupK[EitherT[F, L, ?]] { + implicit val F: Monad[F] + def combineK[A](x: EitherT[F, L, A], y: EitherT[F, L, A]): EitherT[F, L, A] = + EitherT(F.flatMap(x.value) { + case l @ Left(_) => y.value + case r @ Right(_) => F.pure(r) + }) +} + +private[data] trait EitherTFunctor[F[_], L] extends Functor[EitherT[F, L, ?]] { + implicit val F: Functor[F] + override def map[A, B](fa: EitherT[F, L, A])(f: A => B): EitherT[F, L, B] = fa map f +} + +private[data] trait EitherTMonad[F[_], L] extends Monad[EitherT[F, L, ?]] with EitherTFunctor[F, L] { + implicit val F: Monad[F] + def pure[A](a: A): EitherT[F, L, A] = EitherT(F.pure(Either.right(a))) + def flatMap[A, B](fa: EitherT[F, L, A])(f: A => EitherT[F, L, B]): EitherT[F, L, B] = fa flatMap f +} + +private[data] trait EitherTMonadError[F[_], L] extends MonadError[EitherT[F, L, ?], L] with EitherTMonad[F, L] { + def handleErrorWith[A](fea: EitherT[F, L, A])(f: L => EitherT[F, L, A]): EitherT[F, L, A] = + EitherT(F.flatMap(fea.value) { + case Left(e) => f(e).value + case r @ Right(_) => F.pure(r) + }) + override def handleError[A](fea: EitherT[F, L, A])(f: L => A): EitherT[F, L, A] = + EitherT(F.flatMap(fea.value) { + case Left(e) => F.pure(Right(f(e))) + case r @ Right(_) => F.pure(r) + }) + def raiseError[A](e: L): EitherT[F, L, A] = EitherT.left(F.pure(e)) + override def attempt[A](fla: EitherT[F, L, A]): EitherT[F, L, Either[L, A]] = EitherT.right(fla.value) + override def recover[A](fla: EitherT[F, L, A])(pf: PartialFunction[L, A]): EitherT[F, L, A] = + fla.recover(pf) + override def recoverWith[A](fla: EitherT[F, L, A])(pf: PartialFunction[L, EitherT[F, L, A]]): EitherT[F, L, A] = + fla.recoverWith(pf) +} + +private[data] trait EitherTMonadRec[F[_], L] extends MonadRec[EitherT[F, L, ?]] with EitherTMonad[F, L] { + implicit val F: MonadRec[F] + def tailRecM[A, B](a: A)(f: A => EitherT[F, L, Either[A, B]]): EitherT[F, L, B] = + EitherT(F.tailRecM(a)(a0 => F.map(f(a0).value){ + case Left(l) => Right(Left(l)) + case Right(Left(a1)) => Left(a1) + case Right(Right(b)) => Right(Right(b)) + })) +} + +private[data] trait EitherTMonadFilter[F[_], L] extends MonadFilter[EitherT[F, L, ?]] with EitherTMonadError[F, L] { + implicit val F: Monad[F] + implicit val L: Monoid[L] + def empty[A]: EitherT[F, L, A] = EitherT(F.pure(Either.left(L.empty))) +} + +/* TODO violates right absorbtion, right distributivity, and left distributivity -- re-enable when MonadCombine laws are split in to weak/strong +private[data] trait EitherTMonadCombine[F[_], L] extends MonadCombine[EitherT[F, L, ?]] with EitherTMonadFilter[F, L] with EitherTSemigroupK[F, L] { + implicit val F: Monad[F] + implicit val L: Monoid[L] +} +*/ + +private[data] sealed trait EitherTFoldable[F[_], L] extends Foldable[EitherT[F, L, ?]] { + implicit def F0: Foldable[F] + + def foldLeft[A, B](fa: EitherT[F, L, A], b: B)(f: (B, A) => B): B = + fa.foldLeft(b)(f) + + def foldRight[A, B](fa: EitherT[F, L, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = + fa.foldRight(lb)(f) +} + +private[data] sealed trait EitherTTraverse[F[_], L] extends Traverse[EitherT[F, L, ?]] with EitherTFoldable[F, L] { + override implicit def F0: Traverse[F] + + override def traverse[G[_]: Applicative, A, B](fa: EitherT[F, L, A])(f: A => G[B]): G[EitherT[F, L, B]] = + fa traverse f +} + +private[data] sealed trait EitherTBifoldable[F[_]] extends Bifoldable[EitherT[F, ?, ?]] { + implicit def F0: Foldable[F] + + def bifoldLeft[A, B, C](fab: EitherT[F, A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = + F0.foldLeft(fab.value, c)( (acc, axb) => Bifoldable[Either].bifoldLeft(axb, acc)(f, g)) + + def bifoldRight[A, B, C](fab: EitherT[F, A, B], c: Eval[C])(f: (A, Eval[C]) => Eval[C], g: (B, Eval[C]) => Eval[C]): Eval[C] = + F0.foldRight(fab.value, c)( (axb, acc) => Bifoldable[Either].bifoldRight(axb, acc)(f, g)) +} + +private[data] sealed trait EitherTBitraverse[F[_]] extends Bitraverse[EitherT[F, ?, ?]] with EitherTBifoldable[F] { + override implicit def F0: Traverse[F] + + override def bitraverse[G[_], A, B, C, D](fab: EitherT[F, A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[EitherT[F, C, D]] = + fab.bitraverse(f, g) +} + +private[data] sealed trait EitherTEq[F[_], L, A] extends Eq[EitherT[F, L, A]] { + implicit def F0: Eq[F[Either[L, A]]] + + override def eqv(x: EitherT[F, L, A], y: EitherT[F, L, A]): Boolean = x === y +} + +private[data] sealed trait EitherTPartialOrder[F[_], L, A] extends PartialOrder[EitherT[F, L, A]] with EitherTEq[F, L, A]{ + override implicit def F0: PartialOrder[F[Either[L, A]]] + + override def partialCompare(x: EitherT[F, L, A], y: EitherT[F, L, A]): Double = + x partialCompare y +} + +private[data] sealed trait EitherTOrder[F[_], L, A] extends Order[EitherT[F, L, A]] with EitherTPartialOrder[F, L, A]{ + override implicit def F0: Order[F[Either[L, A]]] + + override def compare(x: EitherT[F, L, A], y: EitherT[F, L, A]): Int = x compare y +} diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index 25cdc77474..4ff1ae39c7 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -10,14 +10,14 @@ import cats.functor.Bifunctor * - `[[Ior.Right Right]][B]` * - `[[Ior.Both Both]][A, B]` * - * `A [[Ior]] B` is similar to `A [[Xor]] B`, except that it can represent the simultaneous presence of - * an `A` and a `B`. It is right-biased like [[Xor]], so methods such as `map` and `flatMap` operate on the + * `A [[Ior]] B` is similar to `Either[A, B]`, except that it can represent the simultaneous presence of + * an `A` and a `B`. It is right-biased so methods such as `map` and `flatMap` operate on the * `B` value. Some methods, like `flatMap`, handle the presence of two [[Ior.Both Both]] values using a - * `[[Semigroup]][A]`, while other methods, like [[toXor]], ignore the `A` value in a [[Ior.Both Both]]. + * `[[Semigroup]][A]`, while other methods, like [[toEither]], ignore the `A` value in a [[Ior.Both Both]]. * - * `A [[Ior]] B` is isomorphic to `(A [[Xor]] B) [[Xor]] (A, B)`, but provides methods biased toward `B` + * `A [[Ior]] B` is isomorphic to `Either[Either[A, B], (A, B)]`, but provides methods biased toward `B` * values, regardless of whether the `B` values appear in a [[Ior.Right Right]] or a [[Ior.Both Both]]. - * The isomorphic [[Xor]] form can be accessed via the [[unwrap]] method. + * The isomorphic [[Either]] form can be accessed via the [[unwrap]] method. */ sealed abstract class Ior[+A, +B] extends Product with Serializable { @@ -35,10 +35,10 @@ sealed abstract class Ior[+A, +B] extends Product with Serializable { final def right: Option[B] = fold(_ => None, b => Some(b), (_, b) => Some(b)) final def onlyLeft: Option[A] = fold(a => Some(a), _ => None, (_, _) => None) final def onlyRight: Option[B] = fold(_ => None, b => Some(b), (_, _) => None) - final def onlyLeftOrRight: Option[A Xor B] = fold(a => Some(Xor.left(a)), b => Some(Xor.right(b)), (_, _) => None) + final def onlyLeftOrRight: Option[Either[A, B]] = fold(a => Some(Left(a)), b => Some(Right(b)), (_, _) => None) final def onlyBoth: Option[(A, B)] = fold(_ => None, _ => None, (a, b) => Some((a, b))) final def pad: (Option[A], Option[B]) = fold(a => (Some(a), None), b => (None, Some(b)), (a, b) => (Some(a), Some(b))) - final def unwrap: (A Xor B) Xor (A, B) = fold(a => Xor.left(Xor.left(a)), b => Xor.left(Xor.right(b)), (a, b) => Xor.right((a, b))) + final def unwrap: Either[Either[A, B], (A, B)] = fold(a => Left(Left(a)), b => Left(Right(b)), (a, b) => Right((a, b))) final def toXor: A Xor B = fold(Xor.left, Xor.right, (_, b) => Xor.right(b)) final def toEither: Either[A, B] = fold(Left(_), Right(_), (_, b) => Right(b)) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 72d509d050..1d4eb2aa93 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -101,7 +101,7 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 { new Choice[Kleisli[F, ?, ?]] { def id[A]: Kleisli[F, A, A] = Kleisli(ev.pure(_)) - def choice[A, B, C](f: Kleisli[F, A, C], g: Kleisli[F, B, C]): Kleisli[F, Xor[A, B], C] = + def choice[A, B, C](f: Kleisli[F, A, C], g: Kleisli[F, B, C]): Kleisli[F, Either[A, B], C] = Kleisli(_.fold(f.run, g.run)) def compose[A, B, C](f: Kleisli[F, B, C], g: Kleisli[F, A, B]): Kleisli[F, A, C] = diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 8e70badd01..be0f5a6157 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -181,16 +181,16 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 override def foldRight[A, B](fa: NonEmptyList[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = fa.foldRight(lb)(f) - def tailRecM[A, B](a: A)(f: A => NonEmptyList[A Xor B]): NonEmptyList[B] = { + def tailRecM[A, B](a: A)(f: A => NonEmptyList[Either[A, B]]): NonEmptyList[B] = { val buf = new ListBuffer[B] - @tailrec def go(v: NonEmptyList[A Xor B]): Unit = v.head match { - case Xor.Right(b) => + @tailrec def go(v: NonEmptyList[Either[A, B]]): Unit = v.head match { + case Right(b) => buf += b NonEmptyList.fromList(v.tail) match { case Some(t) => go(t) case None => () } - case Xor.Left(a) => go(f(a) ++ v.tail) + case Left(a) => go(f(a) ++ v.tail) } go(f(a)) NonEmptyList.fromListUnsafe(buf.result()) diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index d80754153e..8727e54a1a 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -182,16 +182,16 @@ private[data] sealed trait NonEmptyVectorInstances { override def foldRight[A, B](fa: NonEmptyVector[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = fa.foldRight(lb)(f) - def tailRecM[A, B](a: A)(f: A => NonEmptyVector[A Xor B]): NonEmptyVector[B] = { + def tailRecM[A, B](a: A)(f: A => NonEmptyVector[Either[A, B]]): NonEmptyVector[B] = { val buf = new VectorBuilder[B] - @tailrec def go(v: NonEmptyVector[A Xor B]): Unit = v.head match { - case Xor.Right(b) => + @tailrec def go(v: NonEmptyVector[Either[A, B]]): Unit = v.head match { + case Right(b) => buf += b NonEmptyVector.fromVector(v.tail) match { case Some(t) => go(t) case None => () } - case Xor.Left(a) => go(f(a).concat(v.tail)) + case Left(a) => go(f(a).concat(v.tail)) } go(f(a)) NonEmptyVector.fromVectorUnsafe(buf.result()) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 6f49a7f35f..7dcd216f3f 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -1,7 +1,8 @@ package cats package data -import instances.option.{catsStdInstancesForOption => optionInstance} +import cats.instances.option.{catsStdInstancesForOption => optionInstance} +import cats.syntax.either._ /** * `OptionT[F[_], A]` is a light wrapper on an `F[Option[A]]` with some @@ -85,11 +86,11 @@ final case class OptionT[F[_], A](value: F[Option[A]]) { case None => default }) - def toRight[L](left: => L)(implicit F: Functor[F]): XorT[F, L, A] = - XorT(cata(Xor.Left(left), Xor.Right.apply)) + def toRight[L](left: => L)(implicit F: Functor[F]): EitherT[F, L, A] = + EitherT(cata(Left(left), Right.apply)) - def toLeft[R](right: => R)(implicit F: Functor[F]): XorT[F, A, R] = - XorT(cata(Xor.Right(right), Xor.Left.apply)) + def toLeft[R](right: => R)(implicit F: Functor[F]): EitherT[F, A, R] = + EitherT(cata(Right(right), Left.apply)) def show(implicit F: Show[F[Option[A]]]): String = F.show(value) @@ -256,9 +257,9 @@ private[data] trait OptionTMonad[F[_]] extends Monad[OptionT[F, ?]] { private[data] trait OptionTMonadRec[F[_]] extends MonadRec[OptionT[F, ?]] with OptionTMonad[F] { implicit def F: MonadRec[F] - def tailRecM[A, B](a: A)(f: A => OptionT[F, A Xor B]): OptionT[F, B] = + def tailRecM[A, B](a: A)(f: A => OptionT[F, Either[A, B]]): OptionT[F, B] = OptionT(F.tailRecM(a)(a0 => F.map(f(a0).value)( - _.fold(Xor.right[A, Option[B]](None))(_.map(Some(_))) + _.fold(Either.right[A, Option[B]](None))(_.map(b => Some(b): Option[B])) ))) } diff --git a/core/src/main/scala/cats/data/StateT.scala b/core/src/main/scala/cats/data/StateT.scala index 13799da11f..ab2fdb4525 100644 --- a/core/src/main/scala/cats/data/StateT.scala +++ b/core/src/main/scala/cats/data/StateT.scala @@ -1,6 +1,8 @@ package cats package data +import cats.syntax.either._ + /** * `StateT[F, S, A]` is similar to `Kleisli[F, S, A]` in that it takes an `S` * argument and produces an `A` value wrapped in `F`. However, it also produces @@ -230,7 +232,7 @@ private[data] sealed trait StateTMonadState[F[_], S] extends MonadState[StateT[F private[data] sealed trait StateTMonadRec[F[_], S] extends MonadRec[StateT[F, S, ?]] with StateTMonad[F, S] { override implicit def F: MonadRec[F] - def tailRecM[A, B](a: A)(f: A => StateT[F, S, A Xor B]): StateT[F, S, B] = + def tailRecM[A, B](a: A)(f: A => StateT[F, S, Either[A, B]]): StateT[F, S, B] = StateT[F, S, B](s => F.tailRecM[(S, A), (S, B)]((s, a)) { case (s, a) => F.map(f(a).run(s)) { case (s, ab) => ab.bimap((s, _), (s, _)) } }) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index cb7cedd3b2..b073ed0e7e 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -80,11 +80,11 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { def toXor: Xor[E, A] = fold(Xor.Left.apply, Xor.Right.apply) /** - * Convert to an Xor, apply a function, convert back. This is handy - * when you want to use the Monadic properties of the Xor type. + * Convert to an Either, apply a function, convert back. This is handy + * when you want to use the Monadic properties of the Either type. */ - def withXor[EE, B](f: (E Xor A) => (EE Xor B)): Validated[EE, B] = - f(toXor).toValidated + def withEither[EE, B](f: Either[E, A] => Either[EE, B]): Validated[EE, B] = + Validated.fromEither(f(toEither)) /** * Validated is a [[functor.Bifunctor]], this method applies one of the @@ -177,7 +177,7 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { * This allows "chained" validation: the output of one validation can be fed * into another validation function. * - * This function is similar to `Xor.flatMap`. It's not called `flatMap`, + * This function is similar to `flatMap` on `Either`. It's not called `flatMap`, * because by Cats convention, `flatMap` is a monadic bind that is consistent * with `ap`. This method is not consistent with [[ap]] (or other * `Apply`-based methods), because it has "fail-fast" behavior as opposed to diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index b7e599063d..56fa4a5055 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -263,11 +263,11 @@ private[data] sealed abstract class XorInstances extends XorInstances1 { def flatMap[B, C](fa: A Xor B)(f: B => A Xor C): A Xor C = fa.flatMap(f) override def ap[B, C](x: A Xor (B => C))(y: A Xor B): A Xor C = y.ap(x) def pure[B](b: B): A Xor B = Xor.right(b) - @tailrec def tailRecM[B, C](b: B)(f: B => A Xor (B Xor C)): A Xor C = + @tailrec def tailRecM[B, C](b: B)(f: B => A Xor Either[B, C]): A Xor C = f(b) match { case Xor.Left(a) => Xor.Left(a) - case Xor.Right(Xor.Left(b1)) => tailRecM(b1)(f) - case Xor.Right(Xor.Right(c)) => Xor.Right(c) + case Xor.Right(Left(b1)) => tailRecM(b1)(f) + case Xor.Right(Right(c)) => Xor.Right(c) } def handleErrorWith[B](fea: Xor[A, B])(f: A => Xor[A, B]): Xor[A, B] = fea match { @@ -278,7 +278,7 @@ private[data] sealed abstract class XorInstances extends XorInstances1 { override def map[B, C](fa: A Xor B)(f: B => C): A Xor C = fa.map(f) override def map2Eval[B, C, Z](fb: A Xor B, fc: Eval[A Xor C])(f: (B, C) => Z): Eval[A Xor Z] = fb.map2Eval(fc)(f) - override def attempt[B](fab: A Xor B): A Xor (A Xor B) = Xor.right(fab) + override def attempt[B](fab: A Xor B): A Xor (Either[A, B]) = Xor.right(fab.toEither) override def recover[B](fab: A Xor B)(pf: PartialFunction[A, B]): A Xor B = fab recover pf override def recoverWith[B](fab: A Xor B)(pf: PartialFunction[A, A Xor B]): A Xor B = diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 5e6a44a5fa..808d081f0a 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -55,6 +55,8 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { def toEither(implicit F: Functor[F]): F[Either[A, B]] = F.map(value)(_.toEither) + def toEitherT(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(toEither) + def toOption(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.toOption)) def to[G[_]](implicit F: Functor[F], G: Alternative[G]): F[G[B]] = @@ -382,7 +384,7 @@ private[data] trait XorTMonadError[F[_], L] extends MonadError[XorT[F, L, ?], L] case r @ Xor.Right(_) => F.pure(r) }) def raiseError[A](e: L): XorT[F, L, A] = XorT.left(F.pure(e)) - override def attempt[A](fla: XorT[F, L, A]): XorT[F, L, L Xor A] = XorT.right(fla.value) + override def attempt[A](fla: XorT[F, L, A]): XorT[F, L, Either[L, A]] = XorT.right(fla.toEither) override def recover[A](fla: XorT[F, L, A])(pf: PartialFunction[L, A]): XorT[F, L, A] = fla.recover(pf) override def recoverWith[A](fla: XorT[F, L, A])(pf: PartialFunction[L, XorT[F, L, A]]): XorT[F, L, A] = @@ -391,11 +393,11 @@ private[data] trait XorTMonadError[F[_], L] extends MonadError[XorT[F, L, ?], L] private[data] trait XorTMonadRec[F[_], L] extends MonadRec[XorT[F, L, ?]] with XorTMonad[F, L] { implicit val F: MonadRec[F] - def tailRecM[A, B](a: A)(f: A => XorT[F, L, A Xor B]): XorT[F, L, B] = + def tailRecM[A, B](a: A)(f: A => XorT[F, L, Either[A, B]]): XorT[F, L, B] = XorT(F.tailRecM(a)(a0 => F.map(f(a0).value){ - case Xor.Left(l) => Xor.Right(Xor.Left(l)) - case Xor.Right(Xor.Left(a1)) => Xor.Left(a1) - case Xor.Right(Xor.Right(b)) => Xor.Right(Xor.Right(b)) + case Xor.Left(l) => Right(Xor.Left(l)) + case Xor.Right(Left(a1)) => Left(a1) + case Xor.Right(Right(b)) => Right(Xor.Right(b)) })) } diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 8bdb39dd64..21c20926cb 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -1,8 +1,8 @@ package cats package instances +import cats.syntax.either._ import scala.annotation.tailrec -import cats.data.Xor trait EitherInstances extends EitherInstances1 { implicit val catsStdBitraverseForEither: Bitraverse[Either] = @@ -26,22 +26,29 @@ trait EitherInstances extends EitherInstances1 { } } - implicit def catsStdInstancesForEither[A]: MonadRec[Either[A, ?]] with Traverse[Either[A, ?]] = - new MonadRec[Either[A, ?]] with Traverse[Either[A, ?]] { + implicit def catsStdInstancesForEither[A]: MonadRec[Either[A, ?]] with MonadError[Either[A, ?], A] with Traverse[Either[A, ?]] = + new MonadRec[Either[A, ?]] with MonadError[Either[A, ?], A] with Traverse[Either[A, ?]] { def pure[B](b: B): Either[A, B] = Right(b) def flatMap[B, C](fa: Either[A, B])(f: B => Either[A, C]): Either[A, C] = fa.right.flatMap(f) + def handleErrorWith[B](fea: Either[A, B])(f: A => Either[A, B]): Either[A, B] = + fea match { + case Left(e) => f(e) + case r @ Right(_) => r + } + def raiseError[B](e: A): Either[A, B] = Left(e) + override def map[B, C](fa: Either[A, B])(f: B => C): Either[A, C] = fa.right.map(f) @tailrec - def tailRecM[B, C](b: B)(f: B => Either[A, B Xor C]): Either[A, C] = + def tailRecM[B, C](b: B)(f: B => Either[A, Either[B, C]]): Either[A, C] = f(b) match { - case Left(a) => Left(a) - case Right(Xor.Left(b1)) => tailRecM(b1)(f) - case Right(Xor.Right(c)) => Right(c) + case Left(a) => Left(a) + case Right(Left(b1)) => tailRecM(b1)(f) + case Right(Right(c)) => Right(c) } override def map2Eval[B, C, Z](fb: Either[A, B], fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = @@ -64,6 +71,14 @@ trait EitherInstances extends EitherInstances1 { def foldRight[B, C](fa: Either[A, B], lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = fa.fold(_ => lc, b => f(b, lc)) + + override def attempt[B](fab: Either[A, B]): Either[A, Either[A, B]] = Right(fab) + override def recover[B](fab: Either[A, B])(pf: PartialFunction[A, B]): Either[A, B] = + fab recover pf + override def recoverWith[B](fab: Either[A, B])(pf: PartialFunction[A, Either[A, B]]): Either[A, B] = + fab recoverWith pf + override def ensure[B](fab: Either[A, B])(error: => A)(predicate: B => Boolean): Either[A, B] = + fab.ensure(error)(predicate) } implicit def catsStdOrderForEither[A, B](implicit A: Order[A], B: Order[B]): Order[Either[A, B]] = new Order[Either[A, B]] { diff --git a/core/src/main/scala/cats/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index 758d620b80..3444518029 100644 --- a/core/src/main/scala/cats/instances/function.scala +++ b/core/src/main/scala/cats/instances/function.scala @@ -2,7 +2,6 @@ package cats package instances import cats.arrow.{Arrow, Choice} -import cats.data.Xor import cats.functor.Contravariant private[instances] sealed trait Function0Instances { @@ -50,10 +49,10 @@ private[instances] sealed trait Function1Instances extends Function1Instances0 { implicit val catsStdInstancesForFunction1: Choice[Function1] with Arrow[Function1] = new Choice[Function1] with Arrow[Function1] { - def choice[A, B, C](f: A => C, g: B => C): Xor[A, B] => C = + def choice[A, B, C](f: A => C, g: B => C): Either[A, B] => C = _ match { - case Xor.Left(a) => f(a) - case Xor.Right(b) => g(b) + case Left(a) => f(a) + case Right(b) => g(b) } def lift[A, B](f: A => B): A => B = f diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index a7d444acf1..b6796e8d46 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -1,8 +1,6 @@ package cats package instances -import cats.data.Xor - import scala.util.control.NonFatal import scala.concurrent.{ExecutionContext, Future} @@ -18,10 +16,10 @@ trait FutureInstances extends FutureInstances1 { * Note that while this implementation will not compile with `@tailrec`, * it is in fact stack-safe. */ - final def tailRecM[B, C](b: B)(f: B => Future[(B Xor C)]): Future[C] = + final def tailRecM[B, C](b: B)(f: B => Future[Either[B, C]]): Future[C] = f(b).flatMap { - case Xor.Left(b1) => tailRecM(b1)(f) - case Xor.Right(c) => Future.successful(c) + case Left(b1) => tailRecM(b1)(f) + case Right(c) => Future.successful(c) } def handleErrorWith[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] = fea.recoverWith { case t => f(t) } @@ -29,8 +27,8 @@ trait FutureInstances extends FutureInstances1 { def raiseError[A](e: Throwable): Future[A] = Future.failed(e) override def handleError[A](fea: Future[A])(f: Throwable => A): Future[A] = fea.recover { case t => f(t) } - override def attempt[A](fa: Future[A]): Future[Throwable Xor A] = - (fa map Xor.right) recover { case NonFatal(t) => Xor.left(t) } + override def attempt[A](fa: Future[A]): Future[Either[Throwable, A]] = + (fa.map(a => Right[Throwable, A](a))) recover { case NonFatal(t) => Left(t) } override def recover[A](fa: Future[A])(pf: PartialFunction[Throwable, A]): Future[A] = fa.recover(pf) diff --git a/core/src/main/scala/cats/instances/list.scala b/core/src/main/scala/cats/instances/list.scala index 9b38b6af21..7e83c95b91 100644 --- a/core/src/main/scala/cats/instances/list.scala +++ b/core/src/main/scala/cats/instances/list.scala @@ -6,8 +6,6 @@ import cats.syntax.show._ import scala.annotation.tailrec import scala.collection.mutable.ListBuffer -import cats.data.Xor - trait ListInstances extends cats.kernel.instances.ListInstances { implicit val catsStdInstancesForList: TraverseFilter[List] with MonadCombine[List] with MonadRec[List] with CoflatMap[List] = @@ -28,12 +26,12 @@ trait ListInstances extends cats.kernel.instances.ListInstances { override def map2[A, B, Z](fa: List[A], fb: List[B])(f: (A, B) => Z): List[Z] = fa.flatMap(a => fb.map(b => f(a, b))) - def tailRecM[A, B](a: A)(f: A => List[A Xor B]): List[B] = { + def tailRecM[A, B](a: A)(f: A => List[Either[A, B]]): List[B] = { val buf = List.newBuilder[B] - @tailrec def go(lists: List[List[A Xor B]]): Unit = lists match { + @tailrec def go(lists: List[List[Either[A, B]]]): Unit = lists match { case (ab :: abs) :: tail => ab match { - case Xor.Right(b) => buf += b; go(abs :: tail) - case Xor.Left(a) => go(f(a) :: abs :: tail) + case Right(b) => buf += b; go(abs :: tail) + case Left(a) => go(f(a) :: abs :: tail) } case Nil :: tail => go(tail) case Nil => () diff --git a/core/src/main/scala/cats/instances/option.scala b/core/src/main/scala/cats/instances/option.scala index 7ce5d7ffc9..d77d4ef81e 100644 --- a/core/src/main/scala/cats/instances/option.scala +++ b/core/src/main/scala/cats/instances/option.scala @@ -2,7 +2,6 @@ package cats package instances import scala.annotation.tailrec -import cats.data.Xor trait OptionInstances extends cats.kernel.instances.OptionInstances { @@ -22,11 +21,11 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances { fa.flatMap(f) @tailrec - def tailRecM[A, B](a: A)(f: A => Option[A Xor B]): Option[B] = + def tailRecM[A, B](a: A)(f: A => Option[Either[A, B]]): Option[B] = f(a) match { case None => None - case Some(Xor.Left(a1)) => tailRecM(a1)(f) - case Some(Xor.Right(b)) => Some(b) + case Some(Left(a1)) => tailRecM(a1)(f) + case Some(Right(b)) => Some(b) } override def map2[A, B, Z](fa: Option[A], fb: Option[B])(f: (A, B) => Z): Option[Z] = diff --git a/core/src/main/scala/cats/instances/try.scala b/core/src/main/scala/cats/instances/try.scala index e92dcdacd7..b87f808126 100644 --- a/core/src/main/scala/cats/instances/try.scala +++ b/core/src/main/scala/cats/instances/try.scala @@ -1,7 +1,6 @@ package cats package instances -import cats.data.Xor import TryInstances.castFailure import scala.util.control.NonFatal @@ -53,11 +52,11 @@ trait TryInstances extends TryInstances1 { case f: Failure[_] => G.pure(castFailure[B](f)) } - @tailrec final def tailRecM[B, C](b: B)(f: B => Try[(B Xor C)]): Try[C] = + @tailrec final def tailRecM[B, C](b: B)(f: B => Try[Either[B, C]]): Try[C] = f(b) match { case f: Failure[_] => castFailure[C](f) - case Success(Xor.Left(b1)) => tailRecM(b1)(f) - case Success(Xor.Right(c)) => Success(c) + case Success(Left(b1)) => tailRecM(b1)(f) + case Success(Right(c)) => Success(c) } def handleErrorWith[A](ta: Try[A])(f: Throwable => Try[A]): Try[A] = @@ -67,8 +66,8 @@ trait TryInstances extends TryInstances1 { override def handleError[A](ta: Try[A])(f: Throwable => A): Try[A] = ta.recover { case t => f(t) } - override def attempt[A](ta: Try[A]): Try[Throwable Xor A] = - (ta map Xor.right) recover { case NonFatal(t) => Xor.left(t) } + override def attempt[A](ta: Try[A]): Try[Either[Throwable, A]] = + (ta.map(a => Right[Throwable, A](a))) recover { case NonFatal(t) => Left(t) } override def recover[A](ta: Try[A])(pf: PartialFunction[Throwable, A]): Try[A] = ta.recover(pf) diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 5ab9678a10..7dd5f7ca03 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -1,5 +1,4 @@ import scala.annotation.tailrec -import cats.data.Xor /** * Symbolic aliases for various types are defined here. @@ -34,9 +33,9 @@ package object cats { def extract[A](a: A): A = a def flatMap[A, B](a: A)(f: A => B): B = f(a) def coflatMap[A, B](a: A)(f: A => B): B = f(a) - @tailrec def tailRecM[A, B](a: A)(f: A => A Xor B): B = f(a) match { - case Xor.Left(a1) => tailRecM(a1)(f) - case Xor.Right(b) => b + @tailrec def tailRecM[A, B](a: A)(f: A => Either[A, B]): B = f(a) match { + case Left(a1) => tailRecM(a1)(f) + case Right(b) => b } override def map[A, B](fa: A)(f: A => B): B = f(fa) override def ap[A, B](ff: A => B)(fa: A): B = ff(fa) diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index 73c57aef64..10dc9a6079 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -1,7 +1,7 @@ package cats package syntax -import cats.data.{Xor, XorT} +import cats.data.EitherT trait ApplicativeErrorSyntax { implicit def catsSyntaxApplicativeErrorId[E](e: E): ApplicativeErrorIdOps[E] = @@ -24,10 +24,10 @@ final class ApplicativeErrorOps[F[_], E, A](fa: F[A])(implicit F: ApplicativeErr def handleErrorWith(f: E => F[A]): F[A] = F.handleErrorWith(fa)(f) - def attempt: F[E Xor A] = + def attempt: F[Either[E, A]] = F.attempt(fa) - def attemptT: XorT[F, E, A] = + def attemptT: EitherT[F, E, A] = F.attemptT(fa) def recover(pf: PartialFunction[E, A]): F[A] = diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index b91efb2635..fb728c2860 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -1,13 +1,163 @@ package cats package syntax -import cats.data.Xor +import cats.data.{Ior, Validated, ValidatedNel, Xor} +import scala.reflect.ClassTag +import scala.util.{Failure, Success, Try} trait EitherSyntax { implicit def catsSyntaxEither[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab) + + implicit def catsSyntaxEitherObject(either: Either.type): EitherObjectOps = new EitherObjectOps(either) } final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { + def foreach(f: B => Unit): Unit = eab.fold(_ => (), f) + + def getOrElse[BB >: B](default: => BB): BB = eab.fold(_ => default, identity) + + def orElse[C, BB >: B](fallback: => Either[C, BB]): Either[C, BB]= eab match { + case Left(_) => fallback + case r @ Right(_) => r.asInstanceOf[Either[C, BB]] + } + + def recover[BB >: B](pf: PartialFunction[A, BB]): Either[A, BB] = eab match { + case Left(a) if pf.isDefinedAt(a) => Right(pf(a)) + case _ => eab + } + + def recoverWith[AA >: A, BB >: B](pf: PartialFunction[A, Either[AA, BB]]): Either[AA, BB] = eab match { + case Left(a) if pf.isDefinedAt(a) => pf(a) + case _ => eab + } + + def valueOr[BB >: B](f: A => BB): BB = eab.fold(f, identity) + + def forall(f: B => Boolean): Boolean = eab.fold(_ => true, f) + + def exists(f: B => Boolean): Boolean = eab.fold(_ => false, f) + + def ensure[AA >: A](onFailure: => AA)(f: B => Boolean): Either[AA, B] = + eab.fold(_ => eab, b => if (f(b)) eab else Left(onFailure)) + + def toIor: A Ior B = eab.fold(Ior.left, Ior.right) + + def toOption: Option[B] = eab.fold(_ => None, Some(_)) + + def toList: List[B] = eab.fold(_ => Nil, _ :: Nil) + + def toTry(implicit ev: A <:< Throwable): Try[B] = eab.fold(a => Failure(ev(a)), Success(_)) + + def toValidated: Validated[A, B] = eab.fold(Validated.Invalid.apply, Validated.Valid.apply) + + /** Returns a [[ValidatedNel]] representation of this disjunction with the `Left` value + * as a single element on the `Invalid` side of the [[NonEmptyList]]. */ + def toValidatedNel[AA >: A]: ValidatedNel[AA, B] = eab.fold(Validated.invalidNel, Validated.valid) + + def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB]): Either[AA, BB] = + f(toValidated).toEither + + def to[F[_], BB >: B](implicit F: Alternative[F]): F[BB] = + eab.fold(_ => F.empty, F.pure) + + def bimap[C, D](fa: A => C, fb: B => D): Either[C, D] = eab match { + case Left(a) => Left(fa(a)) + case Right(b) => Right(fb(b)) + } + + def map[C](f: B => C): Either[A, C] = eab match { + case l @ Left(_) => l.asInstanceOf[Either[A, C]] + case Right(b) => Right(f(b)) + } + + def map2Eval[AA >: A, C, Z](fc: Eval[Either[AA, C]])(f: (B, C) => Z): Eval[Either[AA, Z]] = + eab match { + case l @ Left(_) => Now(l.asInstanceOf[Either[AA, Z]]) + case Right(b) => fc.map(either => new EitherOps(either).map(f(b, _))) + } + + def leftMap[C](f: A => C): Either[C, B] = eab match { + case Left(a) => Left(f(a)) + case r @ Right(_) => r.asInstanceOf[Either[C, B]] + } + + def flatMap[AA >: A, D](f: B => Either[AA, D]): Either[AA, D] = eab match { + case l @ Left(_) => l.asInstanceOf[Either[AA, D]] + case Right(b) => f(b) + } + + def compare[AA >: A, BB >: B](that: Either[AA, BB])(implicit AA: Order[AA], BB: Order[BB]): Int = eab.fold( + a => that.fold(AA.compare(a, _), _ => -1), + b => that.fold(_ => 1, BB.compare(b, _)) + ) + + def partialCompare[AA >: A, BB >: B](that: Either[AA, BB])(implicit AA: PartialOrder[AA], BB: PartialOrder[BB]): Double = + eab.fold( + a => that.fold(AA.partialCompare(a, _), _ => -1), + b => that.fold(_ => 1, BB.partialCompare(b, _)) + ) + + def ===[AA >: A, BB >: B](that: Either[AA, BB])(implicit AA: Eq[AA], BB: Eq[BB]): Boolean = eab.fold( + a => that.fold(AA.eqv(a, _), _ => false), + b => that.fold(_ => false, BB.eqv(b, _)) + ) + + def traverse[F[_], AA >: A, D](f: B => F[D])(implicit F: Applicative[F]): F[Either[AA, D]] = eab match { + case l @ Left(_) => F.pure(l.asInstanceOf[Either[AA, D]]) + case Right(b) => F.map(f(b))(Right(_)) + } + + def foldLeft[C](c: C)(f: (C, B) => C): C = eab.fold(_ => c, f(c, _)) + + def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = + eab.fold(_ => lc, b => f(b, lc)) + + /** + * Combine with another `Either` value. + * + * If this `Either` is a `Left` then it will be returned as-is. + * If this `Either` is a `Right` and `that` `Either` is a left, then `that` will be + * returned. + * If both `Either`s are `Right`s, then the `Semigroup[BB]` instance will be used + * to combine both values and return them as a `Right`. + * Note: If both `Either`s are `Left`s then their values are not combined. Use + * `Validated` if you prefer to combine `Left` values. + * + * Examples: + * {{{ + * scala> import cats.implicits._ + * scala> val l1: Either[String, Int] = Either.left("error 1") + * scala> val l2: Either[String, Int] = Either.left("error 2") + * scala> val r3: Either[String, Int] = Either.right(3) + * scala> val r4: Either[String, Int] = Either.right(4) + * + * scala> l1 combine l2 + * res0: Either[String, Int] = Left(error 1) + * + * scala> l1 combine r3 + * res1: Either[String, Int] = Left(error 1) + * + * scala> r3 combine l1 + * res2: Either[String, Int] = Left(error 1) + * + * scala> r3 combine r4 + * res3: Either[String, Int] = Right(7) + * }}} + */ + final def combine[AA >: A, BB >: B](that: Either[AA, BB])(implicit BB: Semigroup[BB]): Either[AA, BB] = eab match { + case left @ Left(_) => left + case Right(b1) => that match { + case left @ Left(_) => left + case Right(b2) => Right(BB.combine(b1, b2)) + } + } + + def show[AA >: A, BB >: B](implicit AA: Show[AA], BB: Show[BB]): String = eab.fold( + a => s"Left(${AA.show(a)})", + b => s"Right(${BB.show(b)})" + ) + + def ap[AA >: A, BB >: B, C](that: Either[AA, BB => C]): Either[AA, C] = (new EitherOps(that)).flatMap(this.map) /** * Convert a `scala.util.Either` into a [[cats.data.Xor]]. @@ -27,4 +177,60 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { * }}} */ def toXor: A Xor B = Xor.fromEither(eab) + + // TODO + def toEitherT: Int = ??? +} + +final class EitherObjectOps(val either: Either.type) extends AnyVal { + def left[A, B](a: A): Either[A, B] = Left(a) + + def right[A, B](b: B): Either[A, B] = Right(b) + + /** + * Evaluates the specified block, catching exceptions of the specified type and returning them on the left side of + * the resulting `Either`. Uncaught exceptions are propagated. + * + * For example: + * {{{ + * scala> import cats.implicits._ // get syntax for Either + * scala> Either.catchOnly[NumberFormatException] { "foo".toInt } + * res0: Either[NumberFormatException, Int] = Left(java.lang.NumberFormatException: For input string: "foo") + * }}} + */ + def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T] = + new CatchOnlyPartiallyApplied[T] + + def catchNonFatal[A](f: => A): Either[Throwable, A] = + try { + right(f) + } catch { + case scala.util.control.NonFatal(t) => left(t) + } + + /** + * Converts a `Try[A]` to a `Throwable Xor A`. + */ + def fromTry[A](t: Try[A]): Either[Throwable, A] = + t match { + case Failure(e) => left(e) + case Success(v) => right(v) + } + + /** + * Converts an `Option[B]` to an `A Xor B`, where the provided `ifNone` values is returned on + * the left of the `Xor` when the specified `Option` is `None`. + */ + def fromOption[A, B](o: Option[B], ifNone: => A): Either[A, B] = + o.fold(left[A, B](ifNone))(right) +} + +final class CatchOnlyPartiallyApplied[T] private[syntax] { + def apply[A](f: => A)(implicit CT: ClassTag[T], NT: NotNull[T]): Either[T, A] = + try { + Right(f) + } catch { + case t if CT.runtimeClass.isInstance(t) => + Left(t.asInstanceOf[T]) + } } diff --git a/core/src/main/scala/cats/syntax/flatMap.scala b/core/src/main/scala/cats/syntax/flatMap.scala index 057ce263f4..1723dddf83 100644 --- a/core/src/main/scala/cats/syntax/flatMap.scala +++ b/core/src/main/scala/cats/syntax/flatMap.scala @@ -25,10 +25,9 @@ final class FlattenOps[F[_], A](ffa: F[F[A]])(implicit F: FlatMap[F]) { * * Example: * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ - * scala> type ErrorOr[A] = String Xor A - * scala> val x: ErrorOr[ErrorOr[Int]] = Xor.right(Xor.right(3)) + * scala> type ErrorOr[A] = Either[String, A] + * scala> val x: ErrorOr[ErrorOr[Int]] = Right(Right(3)) * scala> x.flatten * res0: ErrorOr[Int] = Right(3) * }}} diff --git a/core/src/main/scala/cats/syntax/monadCombine.scala b/core/src/main/scala/cats/syntax/monadCombine.scala index 53cbbd4fdb..40e53f2449 100644 --- a/core/src/main/scala/cats/syntax/monadCombine.scala +++ b/core/src/main/scala/cats/syntax/monadCombine.scala @@ -33,9 +33,8 @@ final class SeparateOps[F[_], G[_, _], A, B](fgab: F[G[A, B]])(implicit F: Monad * * Example: * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ - * scala> val l: List[Xor[String, Int]] = List(Xor.right(1), Xor.left("error")) + * scala> val l: List[Either[String, Int]] = List(Right(1), Left("error")) * scala> l.separate * res0: (List[String], List[Int]) = (List(error),List(1)) * }}} diff --git a/docs/src/main/tut/validated.md b/docs/src/main/tut/validated.md index 216d5f9704..258fd4af4a 100644 --- a/docs/src/main/tut/validated.md +++ b/docs/src/main/tut/validated.md @@ -305,19 +305,19 @@ val houseNumber = config.parse[Int]("house_number").andThen{ n => The `withXor` method allows you to temporarily turn a `Validated` instance into an `Xor` instance and apply it to a function. ```tut:silent -import cats.data.Xor +import cats.syntax.either._ // get Either#flatMap -def positive(field: String, i: Int): ConfigError Xor Int = { - if (i >= 0) Xor.right(i) - else Xor.left(ParseError(field)) +def positive(field: String, i: Int): Either[ConfigError, Int] = { + if (i >= 0) Right(i) + else Left(ParseError(field)) } ``` Thus. ```tut:book -val houseNumber = config.parse[Int]("house_number").withXor{ xor: ConfigError Xor Int => - xor.flatMap{ i => +val houseNumber = config.parse[Int]("house_number").withEither{ either: Either[ConfigError, Int] => + either.flatMap{ i => positive("house_number", i) } } diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index c027c5b698..7e9e718c05 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -3,7 +3,6 @@ package free import scala.annotation.tailrec -import cats.data.Xor, Xor.{Left, Right} import cats.arrow.FunctionK /** @@ -44,7 +43,7 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * Evaluate a single layer of the free monad. */ @tailrec - final def resume(implicit S: Functor[S]): S[Free[S, A]] Xor A = this match { + final def resume(implicit S: Functor[S]): Either[S[Free[S, A]], A] = this match { case Pure(a) => Right(a) case Suspend(t) => Left(S.map(t)(Pure(_))) case FlatMapped(c, f) => @@ -92,20 +91,20 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * resumption in the context of `S`. */ final def runTailRec(implicit S: MonadRec[S]): S[A] = { - def step(rma: Free[S, A]): S[Xor[Free[S, A], A]] = + def step(rma: Free[S, A]): S[Either[Free[S, A], A]] = rma match { case Pure(a) => - S.pure(Xor.right(a)) + S.pure(Right(a)) case Suspend(ma) => - S.map(ma)(Xor.right(_)) + S.map(ma)(Right(_)) case FlatMapped(curr, f) => curr match { case Pure(x) => - S.pure(Xor.left(f(x))) + S.pure(Left(f(x))) case Suspend(mx) => - S.map(mx)(x => Xor.left(f(x))) + S.map(mx)(x => Left(f(x))) case FlatMapped(prev, g) => - S.pure(Xor.left(prev.flatMap(w => g(w).flatMap(f)))) + S.pure(Left(prev.flatMap(w => g(w).flatMap(f)))) } } S.tailRecM(this)(step) @@ -121,9 +120,9 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { */ final def foldMap[M[_]](f: FunctionK[S, M])(implicit M: MonadRec[M]): M[A] = M.tailRecM(this)(_.step match { - case Pure(a) => M.pure(Xor.right(a)) - case Suspend(sa) => M.map(f(sa))(Xor.right) - case FlatMapped(c, g) => M.map(c.foldMap(f))(cc => Xor.left(g(cc))) + case Pure(a) => M.pure(Right(a)) + case Suspend(sa) => M.map(f(sa))(Right(_)) + case FlatMapped(c, g) => M.map(c.foldMap(f))(cc => Left(g(cc))) }) /** @@ -200,7 +199,7 @@ object Free { def pure[A](a: A): Free[S, A] = Free.pure(a) override def map[A, B](fa: Free[S, A])(f: A => B): Free[S, B] = fa.map(f) def flatMap[A, B](a: Free[S, A])(f: A => Free[S, B]): Free[S, B] = a.flatMap(f) - def tailRecM[A, B](a: A)(f: A => Free[S, A Xor B]): Free[S, B] = + def tailRecM[A, B](a: A)(f: A => Free[S, Either[A, B]]): Free[S, B] = f(a).flatMap(_ match { case Left(a1) => tailRecM(a1)(f) // recursion OK here, since Free is lazy case Right(b) => pure(b) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index d4d6498af2..772b27c67f 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -53,7 +53,7 @@ class FreeTests extends CatsSuite { test("tailRecM is stack safe") { val n = 50000 val fa = MonadRec[Free[Option, ?]].tailRecM(0)(i => - Free.pure[Option, Int Xor Int](if (i < n) Xor.Left(i+1) else Xor.Right(i))) + Free.pure[Option, Either[Int, Int]](if (i < n) Left(i+1) else Right(i))) fa should === (Free.pure[Option, Int](n)) } diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala index ae922e63ba..8e1a97dcaf 100644 --- a/free/src/test/scala/cats/free/InjectTests.scala +++ b/free/src/test/scala/cats/free/InjectTests.scala @@ -3,7 +3,7 @@ package free import cats.arrow.FunctionK import cats.tests.CatsSuite -import cats.data.{Xor, Coproduct} +import cats.data.Coproduct import org.scalacheck._ class InjectTests extends CatsSuite { @@ -91,13 +91,13 @@ class InjectTests extends CatsSuite { test("apply in left") { forAll { (y: Test1[Int]) => - Inject[Test1Algebra, T].inj(y) == Coproduct(Xor.Left(y)) should ===(true) + Inject[Test1Algebra, T].inj(y) == Coproduct(Left(y)) should ===(true) } } test("apply in right") { forAll { (y: Test2[Int]) => - Inject[Test2Algebra, T].inj(y) == Coproduct(Xor.Right(y)) should ===(true) + Inject[Test2Algebra, T].inj(y) == Coproduct(Right(y)) should ===(true) } } diff --git a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala index b912534bf7..6dd12d4e21 100644 --- a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala @@ -1,7 +1,7 @@ package cats package laws -import cats.data.{Xor, XorT} +import cats.data.EitherT // Taken from http://functorial.com/psc-pages/docs/Control/Monad/Error/Class/index.html trait ApplicativeErrorLaws[F[_], E] extends ApplicativeLaws[F] { @@ -19,11 +19,11 @@ trait ApplicativeErrorLaws[F[_], E] extends ApplicativeLaws[F] { def handleErrorPure[A](a: A, f: E => A): IsEq[F[A]] = F.handleError(F.pure(a))(f) <-> F.pure(a) - def raiseErrorAttempt(e: E): IsEq[F[E Xor Unit]] = - F.attempt(F.raiseError[Unit](e)) <-> F.pure(Xor.left(e)) + def raiseErrorAttempt(e: E): IsEq[F[Either[E, Unit]]] = + F.attempt(F.raiseError[Unit](e)) <-> F.pure(Left(e)) - def pureAttempt[A](a: A): IsEq[F[E Xor A]] = - F.attempt(F.pure(a)) <-> F.pure(Xor.right(a)) + def pureAttempt[A](a: A): IsEq[F[Either[E, A]]] = + F.attempt(F.pure(a)) <-> F.pure(Right(a)) def handleErrorWithConsistentWithRecoverWith[A](fa: F[A], f: E => F[A]): IsEq[F[A]] = F.handleErrorWith(fa)(f) <-> F.recoverWith(fa)(PartialFunction(f)) @@ -34,8 +34,8 @@ trait ApplicativeErrorLaws[F[_], E] extends ApplicativeLaws[F] { def recoverConsistentWithRecoverWith[A](fa: F[A], pf: PartialFunction[E, A]): IsEq[F[A]] = F.recover(fa)(pf) <-> F.recoverWith(fa)(pf andThen F.pure) - def attemptConsistentWithAttemptT[A](fa: F[A]): IsEq[XorT[F, E, A]] = - XorT(F.attempt(fa)) <-> F.attemptT(fa) + def attemptConsistentWithAttemptT[A](fa: F[A]): IsEq[EitherT[F, E, A]] = + EitherT(F.attempt(fa)) <-> F.attemptT(fa) } object ApplicativeErrorLaws { diff --git a/laws/src/main/scala/cats/laws/ChoiceLaws.scala b/laws/src/main/scala/cats/laws/ChoiceLaws.scala index 71428186f2..f18f520e20 100644 --- a/laws/src/main/scala/cats/laws/ChoiceLaws.scala +++ b/laws/src/main/scala/cats/laws/ChoiceLaws.scala @@ -2,7 +2,6 @@ package cats package laws import cats.arrow.Choice -import cats.data.Xor import cats.syntax.compose._ /** @@ -11,7 +10,7 @@ import cats.syntax.compose._ trait ChoiceLaws[F[_, _]] extends CategoryLaws[F] { implicit override def F: Choice[F] - def choiceCompositionDistributivity[A, B, C, D](fac: F[A, C], fbc: F[B, C], fcd: F[C, D]): IsEq[F[Xor[A, B], D]] = + def choiceCompositionDistributivity[A, B, C, D](fac: F[A, C], fbc: F[B, C], fcd: F[C, D]): IsEq[F[Either[A, B], D]] = (F.choice(fac, fbc) andThen fcd) <-> F.choice(fac andThen fcd, fbc andThen fcd) } diff --git a/laws/src/main/scala/cats/laws/FlatMapRecLaws.scala b/laws/src/main/scala/cats/laws/FlatMapRecLaws.scala index b6eb45aaa9..a95acf7d04 100644 --- a/laws/src/main/scala/cats/laws/FlatMapRecLaws.scala +++ b/laws/src/main/scala/cats/laws/FlatMapRecLaws.scala @@ -1,7 +1,6 @@ package cats package laws -import cats.data.Xor import cats.syntax.flatMap._ import cats.syntax.functor._ @@ -13,8 +12,8 @@ trait FlatMapRecLaws[F[_]] extends FlatMapLaws[F] { def tailRecMConsistentFlatMap[A](a: A, f: A => F[A]): IsEq[F[A]] = { val bounce = F.tailRecM[(A, Int), A]((a, 1)) { case (a0, i) => - if (i > 0) f(a0).map(a1 => Xor.left((a1, i-1))) - else f(a0).map(Xor.right) + if (i > 0) f(a0).map(a1 => Left((a1, i-1))) + else f(a0).map(Right(_)) } bounce <-> f(a).flatMap(f) } diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala index 036e2a8469..8116da9f03 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala @@ -2,7 +2,7 @@ package cats package laws package discipline -import cats.data.{ Xor, XorT } +import cats.data.EitherT import cats.laws.discipline.CartesianTests.Isomorphisms import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Prop} @@ -22,9 +22,9 @@ trait ApplicativeErrorTests[F[_], E] extends ApplicativeTests[F] { EqFB: Eq[F[B]], EqFC: Eq[F[C]], EqE: Eq[E], - EqFXorEU: Eq[F[E Xor Unit]], - EqFXorEA: Eq[F[E Xor A]], - EqXorTFEA: Eq[XorT[F, E, A]], + EqFEitherEU: Eq[F[Either[E, Unit]]], + EqFEitherEA: Eq[F[Either[E, A]]], + EqEitherTFEA: Eq[EitherT[F, E, A]], EqFABC: Eq[F[(A, B, C)]], iso: Isomorphisms[F] ): RuleSet = { diff --git a/laws/src/main/scala/cats/laws/discipline/ChoiceTests.scala b/laws/src/main/scala/cats/laws/discipline/ChoiceTests.scala index 8b2e42f1b1..0244679d67 100644 --- a/laws/src/main/scala/cats/laws/discipline/ChoiceTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ChoiceTests.scala @@ -3,7 +3,6 @@ package laws package discipline import cats.arrow.Choice -import cats.data.Xor import org.scalacheck.Arbitrary import org.scalacheck.Prop._ @@ -17,7 +16,7 @@ trait ChoiceTests[F[_, _]] extends CategoryTests[F] { ArbFCD: Arbitrary[F[C, D]], EqFAB: Eq[F[A, B]], EqFAD: Eq[F[A, D]], - EqFXorABD: Eq[F[Xor[A, B], D]] + EqFEitherABD: Eq[F[Either[A, B], D]] ): RuleSet = new DefaultRuleSet( name = "choice", diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 732159043b..74bca14988 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -2,7 +2,7 @@ package cats package laws package discipline -import cats.data.{ Xor, XorT } +import cats.data.EitherT import cats.laws.discipline.CartesianTests.Isomorphisms import org.scalacheck.{Arbitrary, Prop} import org.scalacheck.Prop.forAll @@ -21,9 +21,9 @@ trait MonadErrorTests[F[_], E] extends ApplicativeErrorTests[F, E] with MonadTes EqFB: Eq[F[B]], EqFC: Eq[F[C]], EqE: Eq[E], - EqFXorEU: Eq[F[E Xor Unit]], - EqFXorEA: Eq[F[E Xor A]], - EqXorTFEA: Eq[XorT[F, E, A]], + EqFEitherEU: Eq[F[Either[E, Unit]]], + EqFEitherEA: Eq[F[Either[E, A]]], + EqEitherTFEA: Eq[EitherT[F, E, A]], EqFABC: Eq[F[(A, B, C)]], iso: Isomorphisms[F] ): RuleSet = { diff --git a/tests/src/test/scala/cats/tests/ApplicativeErrorTests.scala b/tests/src/test/scala/cats/tests/ApplicativeErrorTests.scala index 78b71ac964..7b8d8ab006 100644 --- a/tests/src/test/scala/cats/tests/ApplicativeErrorTests.scala +++ b/tests/src/test/scala/cats/tests/ApplicativeErrorTests.scala @@ -1,13 +1,9 @@ package cats package tests -import cats.data.{XorT} - +import cats.data.EitherT class ApplicativeErrorCheck extends CatsSuite { - - - val failed: Option[Int] = (()).raiseError[Option, Int] @@ -23,12 +19,12 @@ class ApplicativeErrorCheck extends CatsSuite { failed.handleErrorWith(_ => Some(7)) should === (Some(7)) } - test("attempt syntax creates a wrapped Xor") { - failed.attempt should === (Option(().left)) + test("attempt syntax creates a wrapped Either") { + failed.attempt should === (Option(Left(()))) } - test("attemptT syntax creates an XorT") { - failed.attemptT should === (XorT[Option, Unit, Int](Option(().left))) + test("attemptT syntax creates an EitherT") { + failed.attemptT should === (EitherT[Option, Unit, Int](Option(Left(())))) } test("recover syntax transforms an error to a success") { diff --git a/tests/src/test/scala/cats/tests/CoproductTests.scala b/tests/src/test/scala/cats/tests/CoproductTests.scala index dc2e4c4d55..059c687343 100644 --- a/tests/src/test/scala/cats/tests/CoproductTests.scala +++ b/tests/src/test/scala/cats/tests/CoproductTests.scala @@ -53,9 +53,9 @@ class CoproductTests extends CatsSuite { } } - test("toValidated + toXor is identity") { + test("toValidated + toEither is identity") { forAll { (x: Coproduct[Option, List, Int]) => - x.toValidated.toXor should === (x.run) + x.toValidated.toEither should === (x.run) } } } diff --git a/tests/src/test/scala/cats/tests/IorTests.scala b/tests/src/test/scala/cats/tests/IorTests.scala index eca8a7c9c9..ad79362dc5 100644 --- a/tests/src/test/scala/cats/tests/IorTests.scala +++ b/tests/src/test/scala/cats/tests/IorTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{Xor, Ior} +import cats.data.Ior import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary._ @@ -34,7 +34,7 @@ class IorTests extends CatsSuite { test("onlyLeftOrRight") { forAll { (i: Int Ior String) => - i.onlyLeft.map(Xor.left).orElse(i.onlyRight.map(Xor.right)) should === (i.onlyLeftOrRight) + i.onlyLeft.map(Left(_)).orElse(i.onlyRight.map(Right(_))) should === (i.onlyLeftOrRight) } } @@ -147,9 +147,9 @@ class IorTests extends CatsSuite { } } - test("toXor consistent with right") { + test("toEither consistent with right") { forAll { (x: Int Ior String) => - x.toXor.toOption should === (x.right) + x.toEither.toOption should === (x.right) } } diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index 451a80685c..6153e2bb0c 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -2,7 +2,7 @@ package cats package tests import cats.arrow.{Arrow, Choice, Split, FunctionK} -import cats.data.{XorT, Kleisli, Reader} +import cats.data.{EitherT, Kleisli, Reader} import cats.functor.{Contravariant, Strong} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -15,7 +15,7 @@ class KleisliTests extends CatsSuite { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) - implicit val xorTEq = XorT.catsDataEqForXorT[Kleisli[Option, Int, ?], Unit, Int] + implicit val eitherTEq = EitherT.catsDataEqForEitherT[Kleisli[Option, Int, ?], Unit, Int] implicit val iso = CartesianTests.Isomorphisms.invariant[Kleisli[Option, Int, ?]] @@ -207,4 +207,4 @@ class KleisliTests extends CatsSuite { FlatMap[IntReader] Semigroup[IntReader[String]] } -} \ No newline at end of file +} diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 129c587986..6196f26311 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -98,7 +98,7 @@ object ListWrapper { new MonadRec[ListWrapper] { def pure[A](x: A): ListWrapper[A] = ListWrapper(M.pure(x)) def flatMap[A, B](fa: ListWrapper[A])(f: A => ListWrapper[B]): ListWrapper[B] = ListWrapper(M.flatMap(fa.list)(a => f(a).list)) - def tailRecM[A, B](a: A)(f: A => ListWrapper[cats.data.Xor[A,B]]): ListWrapper[B] = + def tailRecM[A, B](a: A)(f: A => ListWrapper[Either[A,B]]): ListWrapper[B] = ListWrapper(M.tailRecM(a)(a => f(a).list)) } } diff --git a/tests/src/test/scala/cats/tests/MonadRecInstancesTests.scala b/tests/src/test/scala/cats/tests/MonadRecInstancesTests.scala index 5839ab419d..9a455e49eb 100644 --- a/tests/src/test/scala/cats/tests/MonadRecInstancesTests.scala +++ b/tests/src/test/scala/cats/tests/MonadRecInstancesTests.scala @@ -1,12 +1,12 @@ package cats package tests -import cats.data.{OptionT, StateT, Xor, XorT} +import cats.data.{EitherT, OptionT, StateT, Xor, XorT} class MonadRecInstancesTests extends CatsSuite { def tailRecMStackSafety[M[_]](implicit M: MonadRec[M], Eq: Eq[M[Int]]): Unit = { val n = 50000 - val res = M.tailRecM(0)(i => M.pure(if (i < n) Xor.Left(i + 1) else Xor.Right(i))) + val res = M.tailRecM(0)(i => M.pure(if (i < n) Either.left(i + 1) else Either.right(i))) res should === (M.pure(n)) } @@ -34,6 +34,10 @@ class MonadRecInstancesTests extends CatsSuite { tailRecMStackSafety[XorT[Option, String, ?]] } + test("tailRecM stack-safety for EitherT") { + tailRecMStackSafety[EitherT[Option, String, ?]] + } + test("tailRecM stack-safety for List") { tailRecMStackSafety[List] } diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 6d3dd71fd1..31591b5999 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{OptionT, Xor, XorT} +import cats.data.{EitherT, OptionT} import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -82,32 +82,32 @@ class OptionTTests extends CatsSuite { { // F has a MonadError - type SXor[A] = String Xor A + type SEither[A] = Either[String, A] - implicit val monadError = OptionT.catsDataMonadErrorForOptionT[SXor, String] + implicit val monadError = OptionT.catsDataMonadErrorForOptionT[SEither, String] import org.scalacheck.Arbitrary - implicit val arb1 = implicitly[Arbitrary[OptionT[SXor, Int]]] - implicit val arb2 = implicitly[Arbitrary[OptionT[SXor, Int => Int]]] + implicit val arb1 = implicitly[Arbitrary[OptionT[SEither, Int]]] + implicit val arb2 = implicitly[Arbitrary[OptionT[SEither, Int => Int]]] - implicit val eq0 = OptionT.catsDataEqForOptionT[SXor, Option[Int]] - implicit val eq1 = OptionT.catsDataEqForOptionT[SXor, Int] - implicit val eq2 = OptionT.catsDataEqForOptionT[SXor, Unit] - implicit val eq3 = OptionT.catsDataEqForOptionT[SXor, SXor[Unit]] - implicit val eq4 = OptionT.catsDataEqForOptionT[SXor, SXor[Int]] - implicit val eq5 = XorT.catsDataEqForXorT[OptionT[SXor, ?], String, Int] - implicit val eq6 = OptionT.catsDataEqForOptionT[SXor, (Int, Int, Int)] + implicit val eq0 = OptionT.catsDataEqForOptionT[SEither, Option[Int]] + implicit val eq1 = OptionT.catsDataEqForOptionT[SEither, Int] + implicit val eq2 = OptionT.catsDataEqForOptionT[SEither, Unit] + implicit val eq3 = OptionT.catsDataEqForOptionT[SEither, SEither[Unit]] + implicit val eq4 = OptionT.catsDataEqForOptionT[SEither, SEither[Int]] + implicit val eq5 = EitherT.catsDataEqForEitherT[OptionT[SEither, ?], String, Int] + implicit val eq6 = OptionT.catsDataEqForOptionT[SEither, (Int, Int, Int)] - implicit val iso = CartesianTests.Isomorphisms.invariant[OptionT[SXor, ?]] + implicit val iso = CartesianTests.Isomorphisms.invariant[OptionT[SEither, ?]] - checkAll("OptionT[String Xor ?, Int]", MonadErrorTests[OptionT[SXor, ?], String].monadError[Int, Int, Int]) - checkAll("MonadError[OptionT[String Xor ?, ?]]", SerializableTests.serializable(monadError)) + checkAll("OptionT[Either[String, ?], Int]", MonadErrorTests[OptionT[SEither, ?], String].monadError[Int, Int, Int]) + checkAll("MonadError[OptionT[Either[String, ?], ?]]", SerializableTests.serializable(monadError)) - Monad[OptionT[SXor, ?]] - FlatMap[OptionT[SXor, ?]] - Applicative[OptionT[SXor, ?]] - Apply[OptionT[SXor, ?]] - Functor[OptionT[SXor, ?]] + Monad[OptionT[SEither, ?]] + FlatMap[OptionT[SEither, ?]] + Applicative[OptionT[SEither, ?]] + Apply[OptionT[SEither, ?]] + Functor[OptionT[SEither, ?]] } { @@ -233,9 +233,9 @@ class OptionTTests extends CatsSuite { } } - test("OptionT[Id, A].toRight consistent with Xor.fromOption") { + test("OptionT[Id, A].toRight consistent with Either.fromOption") { forAll { (o: OptionT[Id, Int], s: String) => - o.toRight(s).value should === (Xor.fromOption(o.value, s)) + o.toRight(s).value should === (Either.fromOption(o.value, s)) } } @@ -270,8 +270,8 @@ class OptionTTests extends CatsSuite { } test("show") { - val xor: String Xor Option[Int] = Xor.right(Some(1)) - OptionT[Xor[String, ?], Int](xor).show should === ("Xor.Right(Some(1))") + val xor: Either[String, Option[Int]] = Either.right(Some(1)) + OptionT[Either[String, ?], Int](xor).show should === ("Right(Some(1))") } test("none") { diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 7ca9b3d55d..646e6f83da 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{NonEmptyList, Validated, ValidatedNel, Xor, XorT} +import cats.data.{EitherT, NonEmptyList, Validated, ValidatedNel, Xor} import cats.data.Validated.{Valid, Invalid} import cats.laws.discipline.{BitraverseTests, TraverseTests, ApplicativeErrorTests, SerializableTests, CartesianTests} import org.scalacheck.Arbitrary._ @@ -19,10 +19,10 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[?, ?]", BitraverseTests[Validated].bitraverse[Option, Int, Int, Int, String, String, String]) checkAll("Bitraverse[Validated]", SerializableTests.serializable(Bitraverse[Validated])) - implicit val eq0 = XorT.catsDataEqForXorT[Validated[String, ?], String, Int] + implicit val eq0 = EitherT.catsDataEqForEitherT[Validated[String, ?], String, Int] checkAll("Validated[String, Int]", ApplicativeErrorTests[Validated[String, ?], String].applicativeError[Int, Int, Int]) - checkAll("ApplicativeError[Xor, String]", SerializableTests.serializable(ApplicativeError[Validated[String, ?], String])) + checkAll("ApplicativeError[Validated, String]", SerializableTests.serializable(ApplicativeError[Validated[String, ?], String])) checkAll("Validated[String, Int] with Option", TraverseTests[Validated[String,?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Validated[String, ?]]", SerializableTests.serializable(Traverse[Validated[String,?]])) @@ -140,9 +140,9 @@ class ValidatedTests extends CatsSuite { } } - test("andThen consistent with Xor's flatMap"){ + test("andThen consistent with Either's flatMap"){ forAll { (v: Validated[String, Int], f: Int => Validated[String, Int]) => - v.andThen(f) should === (v.withXor(_.flatMap(f(_).toXor))) + v.andThen(f) should === (v.withEither(_.flatMap(f(_).toEither))) } } diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index ecbc6e9767..c02ad7f7ff 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{Validated, Writer, WriterT, XorT} +import cats.data.{EitherT, Validated, Writer, WriterT} import cats.functor.{Bifunctor, Contravariant} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -322,8 +322,8 @@ class WriterTTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[WriterT[Validated[String, ?], ListWrapper[Int], ?]] implicit def eq1[A:Eq]: Eq[WriterT[Validated[String, ?], ListWrapper[Int], A]] = WriterT.catsDataEqForWriterT[Validated[String, ?], ListWrapper[Int], A] - implicit val eq2: Eq[XorT[WriterT[Validated[String, ?], ListWrapper[Int], ?], String, Int]] = - XorT.catsDataEqForXorT[WriterT[Validated[String, ?], ListWrapper[Int], ?], String, Int] + implicit val eq2: Eq[EitherT[WriterT[Validated[String, ?], ListWrapper[Int], ?], String, Int]] = + EitherT.catsDataEqForEitherT[WriterT[Validated[String, ?], ListWrapper[Int], ?], String, Int] implicit def arb0[A:Arbitrary]: Arbitrary[WriterT[Validated[String, ?], ListWrapper[Int], A]] = arbitrary.catsLawsArbitraryForWriterT[Validated[String, ?], ListWrapper[Int], A] @@ -339,7 +339,7 @@ class WriterTTests extends CatsSuite { // F has a MonadError and L has a Monoid implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] implicit val iso = CartesianTests.Isomorphisms.invariant[WriterT[Option, ListWrapper[Int], ?]] - implicit val eq0: Eq[XorT[WriterT[Option, ListWrapper[Int], ?], Unit, Int]] = XorT.catsDataEqForXorT[WriterT[Option, ListWrapper[Int], ?], Unit, Int] + implicit val eq0: Eq[EitherT[WriterT[Option, ListWrapper[Int], ?], Unit, Int]] = EitherT.catsDataEqForEitherT[WriterT[Option, ListWrapper[Int], ?], Unit, Int] Functor[WriterT[Option, ListWrapper[Int], ?]] diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 52d86570b7..dfda12bbc6 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -2,7 +2,7 @@ package cats package tests import cats.functor.Bifunctor -import cats.data.{Xor, XorT} +import cats.data.{EitherT, Xor, XorT} import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.kernel.laws.{OrderLaws, GroupLaws} @@ -46,8 +46,8 @@ class XorTTests extends CatsSuite { { //if a Monad is defined implicit val F = ListWrapper.monad - implicit val eq0 = XorT.catsDataEqForXorT[ListWrapper, String, String Xor Int] - implicit val eq1 = XorT.catsDataEqForXorT[XorT[ListWrapper, String, ?], String, Int](eq0) + implicit val eq0 = XorT.catsDataEqForXorT[ListWrapper, String, Either[String, Int]] + implicit val eq1 = EitherT.catsDataEqForEitherT[XorT[ListWrapper, String, ?], String, Int](eq0) Functor[XorT[ListWrapper, String, ?]] Applicative[XorT[ListWrapper, String, ?]] diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index 6013e40ee7..ef289473bd 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{NonEmptyList, Xor, XorT} +import cats.data.{EitherT, NonEmptyList, Xor} import cats.data.Xor._ import cats.laws.discipline.{SemigroupKTests} import cats.laws.discipline.arbitrary._ @@ -22,7 +22,7 @@ class XorTests extends CatsSuite { checkAll("Xor[String, NonEmptyList[Int]]", GroupLaws[Xor[String, NonEmptyList[Int]]].semigroup) - implicit val eq0 = XorT.catsDataEqForXorT[Xor[String, ?], String, Int] + implicit val eq0 = EitherT.catsDataEqForEitherT[Xor[String, ?], String, Int] checkAll("Xor[String, Int]", MonadErrorTests[Xor[String, ?], String].monadError[Int, Int, Int]) checkAll("MonadError[Xor, String]", SerializableTests.serializable(MonadError[Xor[String, ?], String])) From a43527f3ecbab56ef7555567012a379588f6c39e Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 14 Aug 2016 20:27:23 -0700 Subject: [PATCH 07/17] Add Either(T) tests and replace Xor with Either in tests --- core/src/main/scala/cats/Bitraverse.scala | 17 +- core/src/main/scala/cats/data/EitherT.scala | 1 - .../main/scala/cats/functor/Bifunctor.scala | 5 +- .../main/scala/cats/instances/either.scala | 14 + core/src/main/scala/cats/syntax/either.scala | 15 +- free/src/test/scala/cats/free/FreeTests.scala | 13 +- .../test/scala/cats/tests/FutureTests.scala | 7 +- .../test/scala/cats/tests/FutureTests.scala | 7 +- .../cats/laws/discipline/Arbitrary.scala | 3 + .../scala/cats/tests/BifoldableTests.scala | 12 +- .../scala/cats/tests/BitraverseTests.scala | 12 +- .../test/scala/cats/tests/EitherTTests.scala | 344 ++++++++++++++++++ .../test/scala/cats/tests/EitherTests.scala | 191 +++++++++- .../scala/cats/tests/MonadCombineTests.scala | 9 +- .../test/scala/cats/tests/OptionTTests.scala | 4 +- .../scala/cats/tests/RegressionTests.scala | 24 +- .../scala/cats/tests/TransLiftTests.scala | 5 +- .../test/scala/cats/tests/UnapplyTests.scala | 4 +- .../scala/cats/tests/ValidatedTests.scala | 6 +- 19 files changed, 616 insertions(+), 77 deletions(-) create mode 100644 tests/src/test/scala/cats/tests/EitherTTests.scala diff --git a/core/src/main/scala/cats/Bitraverse.scala b/core/src/main/scala/cats/Bitraverse.scala index fdb7708082..27767141f4 100644 --- a/core/src/main/scala/cats/Bitraverse.scala +++ b/core/src/main/scala/cats/Bitraverse.scala @@ -17,24 +17,23 @@ import simulacrum.typeclass * * Example: * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ * - * scala> val rightSome: Option[String] Xor Option[Int] = Xor.right(Some(3)) + * scala> val rightSome: Either[Option[String], Option[Int]] = Either.right(Some(3)) * scala> rightSome.bisequence - * res0: Option[String Xor Int] = Some(Right(3)) + * res0: Option[Either[String, Int]] = Some(Right(3)) * - * scala> val rightNone: Option[String] Xor Option[Int] = Xor.right(None) + * scala> val rightNone: Either[Option[String], Option[Int]] = Either.right(None) * scala> rightNone.bisequence - * res1: Option[String Xor Int] = None + * res1: Option[Either[String, Int]] = None * - * scala> val leftSome: Option[String] Xor Option[Int] = Xor.left(Some("foo")) + * scala> val leftSome: Either[Option[String], Option[Int]] = Either.left(Some("foo")) * scala> leftSome.bisequence - * res2: Option[String Xor Int] = Some(Left(foo)) + * res2: Option[Either[String, Int]] = Some(Left(foo)) * - * scala> val leftNone: Option[String] Xor Option[Int] = Xor.left(None) + * scala> val leftNone: Either[Option[String], Option[Int]] = Either.left(None) * scala> leftNone.bisequence - * res3: Option[String Xor Int] = None + * res3: Option[Either[String, Int]] = None * }}} */ def bisequence[G[_]: Applicative, A, B](fab: F[G[A], G[B]]): G[F[A, B]] = diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index c1acc2525e..1e58910f61 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -13,7 +13,6 @@ import cats.syntax.either._ * and lifted in to a `EitherT[F, C, B]` via `EitherT.left`. */ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { - def fold[C](fa: A => C, fb: B => C)(implicit F: Functor[F]): F[C] = F.map(value)(_.fold(fa, fb)) def isLeft(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.isLeft) diff --git a/core/src/main/scala/cats/functor/Bifunctor.scala b/core/src/main/scala/cats/functor/Bifunctor.scala index 0d23fc9177..c0140d6f34 100644 --- a/core/src/main/scala/cats/functor/Bifunctor.scala +++ b/core/src/main/scala/cats/functor/Bifunctor.scala @@ -41,12 +41,11 @@ import simulacrum.typeclass * Widens A into a supertype AA. * Example: * {{{ - * scala> import cats.data.Xor * scala> import cats.implicits._ * scala> sealed trait Foo * scala> case object Bar extends Foo - * scala> val x1: Xor[Bar.type, Int] = Xor.left(Bar) - * scala> val x2: Xor[Foo, Int] = x1.leftWiden + * scala> val x1: Either[Bar.type, Int] = Either.left(Bar) + * scala> val x2: Either[Foo, Int] = x1.leftWiden * }}} */ def leftWiden[A, B, AA >: A](fab: F[A, B]): F[AA, B] = fab.asInstanceOf[F[AA, B]] diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 45a4534358..be73acc698 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -95,6 +95,20 @@ trait EitherInstances extends EitherInstances1 { b => s"Right(${B.show(b)})" ) } + + implicit def catsDataMonoidForEither[A, B](implicit B: Monoid[B]): Monoid[Either[A, B]] = + new Monoid[Either[A, B]] { + def empty: Either[A, B] = Right(B.empty) + def combine(x: Either[A, B], y: Either[A, B]): Either[A, B] = x combine y + } + + implicit def catsDataSemigroupKForEither[L]: SemigroupK[Either[L, ?]] = + new SemigroupK[Either[L, ?]] { + def combineK[A](x: Either[L, A], y: Either[L, A]): Either[L, A] = x match { + case Left(_) => y + case Right(_) => x + } + } } private[instances] sealed trait EitherInstances1 extends EitherInstances2 { diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index fb728c2860..782db63616 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -1,7 +1,7 @@ package cats package syntax -import cats.data.{Ior, Validated, ValidatedNel, Xor} +import cats.data.{EitherT, Ior, Validated, ValidatedNel, Xor} import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} @@ -178,8 +178,17 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { */ def toXor: A Xor B = Xor.fromEither(eab) - // TODO - def toEitherT: Int = ??? + /** + * Transform the `Either` into a [[EitherT]] while lifting it into the specified Applicative. + * + * {{{ + * scala> import cats.implicits._ + * scala> val e: Either[String, Int] = Right(3) + * scala> e.toEitherT[Option] + * res0: cats.data.EitherT[Option, String, Int] = EitherT(Some(Right(3))) + * }}} + */ + def toEitherT[F[_]: Applicative]: EitherT[F, A, B] = EitherT.fromEither(eab) } final class EitherObjectOps(val either: Either.type) extends AnyVal { diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index a219b1a5ed..676a425c5f 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -3,7 +3,6 @@ package free import cats.tests.CatsSuite import cats.arrow.FunctionK -import cats.data.Xor import cats.laws.discipline.{CartesianTests, MonadTests, SerializableTests} import cats.laws.discipline.arbitrary.catsLawsArbitraryForFn0 @@ -92,18 +91,18 @@ class FreeTests extends CatsSuite { // changing the constant argument to .take and observing the time // this test takes. val ns = Stream.from(1).take(1000) - val res = Free.foldLeftM[Stream, Xor[Int, ?], Int, Int](ns, 0) { (sum, n) => - if (sum >= 2) Xor.left(sum) else Xor.right(sum + n) + val res = Free.foldLeftM[Stream, Either[Int, ?], Int, Int](ns, 0) { (sum, n) => + if (sum >= 2) Either.left(sum) else Either.right(sum + n) } - assert(res == Xor.left(3)) + assert(res == Either.left(3)) } test(".foldLeftM short-circuiting") { val ns = Stream.continually(1) - val res = Free.foldLeftM[Stream, Xor[Int, ?], Int, Int](ns, 0) { (sum, n) => - if (sum >= 100000) Xor.left(sum) else Xor.right(sum + n) + val res = Free.foldLeftM[Stream, Either[Int, ?], Int, Int](ns, 0) { (sum, n) => + if (sum >= 100000) Either.left(sum) else Either.right(sum + n) } - assert(res == Xor.left(100000)) + assert(res == Either.left(100000)) } } diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 17d69da4fd..ec982d8817 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -2,7 +2,6 @@ package cats package js package tests -import cats.data.Xor import cats.laws.discipline._ import cats.js.instances.Await import cats.js.instances.future.futureComonad @@ -25,13 +24,13 @@ import DeprecatedForwarder.runNow class FutureTests extends CatsSuite { val timeout = 3.seconds - def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] = - f.map(Xor.right[Throwable, A]).recover { case t => Xor.left(t) } + def futureEither[A](f: Future[A]): Future[Either[Throwable, A]] = + f.map(Either.right[Throwable, A]).recover { case t => Either.left(t) } implicit def eqfa[A: Eq]: Eq[Future[A]] = new Eq[Future[A]] { def eqv(fx: Future[A], fy: Future[A]): Boolean = { - val fz = futureXor(fx) zip futureXor(fy) + val fz = futureEither(fx) zip futureEither(fy) Await.result(fz.map { case (tx, ty) => tx === ty }, timeout) } } diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 0c283bebb9..af68328617 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -2,7 +2,6 @@ package cats package jvm package tests -import cats.data.Xor import cats.laws.discipline._ import cats.tests.CatsSuite @@ -16,13 +15,13 @@ import org.scalacheck.Arbitrary.arbitrary class FutureTests extends CatsSuite { val timeout = 3.seconds - def futureXor[A](f: Future[A]): Future[Xor[Throwable, A]] = - f.map(Xor.right[Throwable, A]).recover { case t => Xor.left(t) } + def futureEither[A](f: Future[A]): Future[Either[Throwable, A]] = + f.map(Either.right[Throwable, A]).recover { case t => Either.left(t) } implicit def eqfa[A: Eq]: Eq[Future[A]] = new Eq[Future[A]] { def eqv(fx: Future[A], fy: Future[A]): Boolean = { - val fz = futureXor(fx) zip futureXor(fy) + val fz = futureEither(fx) zip futureEither(fy) Await.result(fz.map { case (tx, ty) => tx === ty }, timeout) } } diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 344ce42f1b..4e9801c6e7 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -41,6 +41,9 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForXorT[F[_], A, B](implicit F: Arbitrary[F[A Xor B]]): Arbitrary[XorT[F, A, B]] = Arbitrary(F.arbitrary.map(XorT(_))) + implicit def catsLawsArbitraryForEitherT[F[_], A, B](implicit F: Arbitrary[F[Either[A, B]]]): Arbitrary[EitherT[F, A, B]] = + Arbitrary(F.arbitrary.map(EitherT(_))) + implicit def catsLawsArbitraryForValidated[A, B](implicit A: Arbitrary[A], B: Arbitrary[B]): Arbitrary[Validated[A, B]] = Arbitrary(Gen.oneOf(A.arbitrary.map(Validated.invalid), B.arbitrary.map(Validated.valid))) diff --git a/tests/src/test/scala/cats/tests/BifoldableTests.scala b/tests/src/test/scala/cats/tests/BifoldableTests.scala index 2c121909bd..8dc75de41c 100644 --- a/tests/src/test/scala/cats/tests/BifoldableTests.scala +++ b/tests/src/test/scala/cats/tests/BifoldableTests.scala @@ -1,15 +1,13 @@ package cats package tests -import cats.data.Xor import cats.laws.discipline.{BifoldableTests, SerializableTests} -import cats.laws.discipline.arbitrary._ class BifoldableTest extends CatsSuite { - type EitherXor[A, B] = Either[Xor[A, B], Xor[A, B]] - val eitherComposeXor: Bifoldable[EitherXor] = - Bifoldable[Either].compose[Xor] + type EitherEither[A, B] = Either[Either[A, B], Either[A, B]] + val eitherComposeEither: Bifoldable[EitherEither] = + Bifoldable[Either].compose[Either] - checkAll("Either compose Xor", BifoldableTests(eitherComposeXor).bifoldable[Int, Int, Int]) - checkAll("Bifoldable[Either compose Xor]", SerializableTests.serializable(eitherComposeXor)) + checkAll("Either compose Either", BifoldableTests(eitherComposeEither).bifoldable[Int, Int, Int]) + checkAll("Bifoldable[Either compose Either]", SerializableTests.serializable(eitherComposeEither)) } diff --git a/tests/src/test/scala/cats/tests/BitraverseTests.scala b/tests/src/test/scala/cats/tests/BitraverseTests.scala index 989c21822a..f438b36b19 100644 --- a/tests/src/test/scala/cats/tests/BitraverseTests.scala +++ b/tests/src/test/scala/cats/tests/BitraverseTests.scala @@ -1,15 +1,13 @@ package cats package tests -import cats.data.Xor import cats.laws.discipline.{BitraverseTests, SerializableTests} -import cats.laws.discipline.arbitrary._ class BitraverseTest extends CatsSuite { - type XorTuple2[A, B] = Xor[(A, B), (A, B)] - val xorComposeTuple2: Bitraverse[XorTuple2] = - Bitraverse[Xor].compose[Tuple2] + type EitherTuple2[A, B] = Either[(A, B), (A, B)] + val eitherComposeTuple2: Bitraverse[EitherTuple2] = + Bitraverse[Either].compose[Tuple2] - checkAll("Xor compose Tuple2", BitraverseTests(xorComposeTuple2).bitraverse[Option, Int, Int, Int, String, String, String]) - checkAll("Bitraverse[Xor compose Tuple2]", SerializableTests.serializable(xorComposeTuple2)) + checkAll("Either compose Tuple2", BitraverseTests(eitherComposeTuple2).bitraverse[Option, Int, Int, Int, String, String, String]) + checkAll("Bitraverse[Either compose Tuple2]", SerializableTests.serializable(eitherComposeTuple2)) } diff --git a/tests/src/test/scala/cats/tests/EitherTTests.scala b/tests/src/test/scala/cats/tests/EitherTTests.scala new file mode 100644 index 0000000000..94ba2b4122 --- /dev/null +++ b/tests/src/test/scala/cats/tests/EitherTTests.scala @@ -0,0 +1,344 @@ +package cats +package tests + +import cats.data.EitherT +import cats.functor.Bifunctor +import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ +import cats.kernel.laws.{OrderLaws, GroupLaws} + +class EitherTTests extends CatsSuite { + implicit val iso = CartesianTests.Isomorphisms.invariant[EitherT[ListWrapper, String, ?]](EitherT.catsDataFunctorForEitherT(ListWrapper.functor)) + + { + checkAll("EitherT[Option, ListWrapper[String], ?]", SemigroupKTests[EitherT[Option, ListWrapper[String], ?]].semigroupK[Int]) + checkAll("SemigroupK[EitherT[Option, ListWrapper[String], ?]]", SerializableTests.serializable(SemigroupK[EitherT[Option, ListWrapper[String], ?]])) + } + + { + implicit val F = ListWrapper.order[Either[String, Int]] + + checkAll("EitherT[List, String, Int]", OrderLaws[EitherT[ListWrapper, String, Int]].order) + checkAll("Order[EitherT[List, String, Int]]", SerializableTests.serializable(Order[EitherT[ListWrapper, String, Int]])) + } + + { + //If a Functor for F is defined + implicit val F = ListWrapper.functor + + checkAll("EitherT[ListWrapper, ?, ?]", BifunctorTests[EitherT[ListWrapper, ?, ?]].bifunctor[Int, Int, Int, String, String, String]) + checkAll("Bifunctor[EitherT[ListWrapper, ?, ?]]", SerializableTests.serializable(Bifunctor[EitherT[ListWrapper, ?, ?]])) + checkAll("EitherT[ListWrapper, Int, ?]", FunctorTests[EitherT[ListWrapper, Int, ?]].functor[Int, Int, Int]) + checkAll("Functor[EitherT[ListWrapper, Int, ?]]", SerializableTests.serializable(Functor[EitherT[ListWrapper, Int, ?]])) + } + + { + //If a Traverse for F is defined + implicit val F = ListWrapper.traverse + + checkAll("EitherT[ListWrapper, Int, ?]", TraverseTests[EitherT[ListWrapper, Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) + checkAll("Traverse[EitherT[ListWrapper, Int, ?]]", SerializableTests.serializable(Traverse[EitherT[ListWrapper, Int, ?]])) + checkAll("EitherT[ListWrapper, ?, ?]", BitraverseTests[EitherT[ListWrapper, ?, ?]].bitraverse[Option, Int, Int, Int, String, String, String]) + checkAll("Bitraverse[EitherT[ListWrapper, ?, ?]]", SerializableTests.serializable(Bitraverse[EitherT[ListWrapper, ?, ?]])) + + } + + { + //if a Monad is defined + implicit val F = ListWrapper.monad + implicit val eq0 = EitherT.catsDataEqForEitherT[ListWrapper, String, Either[String, Int]] + implicit val eq1 = EitherT.catsDataEqForEitherT[EitherT[ListWrapper, String, ?], String, Int](eq0) + + Functor[EitherT[ListWrapper, String, ?]] + Applicative[EitherT[ListWrapper, String, ?]] + Monad[EitherT[ListWrapper, String, ?]] + + checkAll("EitherT[ListWrapper, String, Int]", MonadErrorTests[EitherT[ListWrapper, String, ?], String].monadError[Int, Int, Int]) + checkAll("MonadError[EitherT[List, ?, ?]]", SerializableTests.serializable(MonadError[EitherT[ListWrapper, String, ?], String])) + } + + { + //if a Monad is defined + implicit val F = ListWrapper.monad + + Functor[EitherT[ListWrapper, String, ?]] + Applicative[EitherT[ListWrapper, String, ?]] + Monad[EitherT[ListWrapper, String, ?]] + + checkAll("EitherT[ListWrapper, String, Int]", MonadTests[EitherT[ListWrapper, String, ?]].monad[Int, Int, Int]) + checkAll("Monad[EitherT[ListWrapper, String, ?]]", SerializableTests.serializable(Monad[EitherT[ListWrapper, String, ?]])) + } + + { + //If a foldable is defined + implicit val F = ListWrapper.foldable + + checkAll("EitherT[ListWrapper, Int, ?]", FoldableTests[EitherT[ListWrapper, Int, ?]].foldable[Int, Int]) + checkAll("Foldable[EitherT[ListWrapper, Int, ?]]", SerializableTests.serializable(Foldable[EitherT[ListWrapper, Int, ?]])) + } + + { + implicit val F = ListWrapper.partialOrder[Either[String, Int]] + + checkAll("EitherT[ListWrapper, String, Int]", OrderLaws[EitherT[ListWrapper, String, Int]].partialOrder) + checkAll("PartialOrder[EitherT[ListWrapper, String, Int]]", SerializableTests.serializable(PartialOrder[EitherT[ListWrapper, String, Int]])) + } + + { + implicit val F = ListWrapper.semigroup[Either[String, Int]] + + checkAll("EitherT[ListWrapper, String, Int]", GroupLaws[EitherT[ListWrapper, String, Int]].semigroup) + checkAll("Semigroup[EitherT[ListWrapper, String, Int]]", SerializableTests.serializable(Semigroup[EitherT[ListWrapper, String, Int]])) + } + + { + implicit val F = ListWrapper.monoid[Either[String, Int]] + + Semigroup[EitherT[ListWrapper, String, Int]] + + checkAll("EitherT[ListWrapper, String, Int]", GroupLaws[EitherT[ListWrapper, String, Int]].monoid) + checkAll("Monoid[EitherT[ListWrapper, String, Int]]", SerializableTests.serializable(Monoid[EitherT[ListWrapper, String, Int]])) + } + + { + implicit val F = ListWrapper.eqv[Either[String, Int]] + + checkAll("EitherT[ListWrapper, String, Int]", OrderLaws[EitherT[ListWrapper, String, Int]].eqv) + checkAll("Eq[EitherT[ListWrapper, String, Int]]", SerializableTests.serializable(Eq[EitherT[ListWrapper, String, Int]])) + } + + test("toValidated") { + forAll { (eithert: EitherT[List, String, Int]) => + eithert.toValidated.map(_.toEither) should === (eithert.value) + } + } + + test("toValidatedNel") { + forAll { (eithert: EitherT[List, String, Int]) => + eithert.toValidatedNel.map(_.toEither.leftMap(_.head)) should === (eithert.value) + } + } + + test("withValidated") { + forAll { (eithert: EitherT[List, String, Int], f: String => Char, g: Int => Double) => + eithert.withValidated(_.bimap(f, g)) should === (eithert.bimap(f, g)) + } + } + + test("fromEither") { + forAll { (either: Either[String, Int]) => + Some(either.isLeft) should === (EitherT.fromEither[Option](either).isLeft) + } + } + + test("isLeft negation of isRight") { + forAll { (eithert: EitherT[List, String, Int]) => + eithert.isLeft should === (eithert.isRight.map(! _)) + } + } + + test("double swap is noop") { + forAll { (eithert: EitherT[List, String, Int]) => + eithert.swap.swap should === (eithert) + } + } + + test("swap negates isRight") { + forAll { (eithert: EitherT[List, String, Int]) => + eithert.swap.isRight should === (eithert.isRight.map(! _)) + } + } + + test("toOption on Right returns Some") { + forAll { (eithert: EitherT[List, String, Int]) => + eithert.toOption.isDefined should === (eithert.isRight) + } + } + + test("toEither preserves isRight") { + forAll { (eithert: EitherT[List, String, Int]) => + eithert.value.map(_.isRight) should === (eithert.isRight) + } + } + + test("recover recovers handled values") { + val eithert = EitherT.left[Id, String, Int]("eithert") + eithert.recover { case "eithert" => 5 }.isRight should === (true) + } + + test("recover ignores unhandled values") { + val eithert = EitherT.left[Id, String, Int]("eithert") + eithert.recover { case "noteithert" => 5 } should === (eithert) + } + + test("recover ignores the right side") { + val eithert = EitherT.right[Id, String, Int](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) + } + + 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) + } + + 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) + } + + 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))) + } + } + + test("semiflatMap consistent with value.flatMap+f+pure") { + forAll { (eithert: EitherT[List, String, Int], f: Int => List[String]) => + eithert.semiflatMap(f) should === (EitherT(eithert.value.flatMap { + case l @ Left(_) => List(l.asInstanceOf[Either[String, String]]) + case Right(b) => f(b).map(Right(_)) + })) + } + } + + test("subflatMap consistent with value.map+flatMap") { + forAll { (eithert: EitherT[List, String, Int], f: Int => Either[String, Double]) => + eithert.subflatMap(f) should === (EitherT(eithert.value.map(_.flatMap(f)))) + } + } + + test("fold with Id consistent with Either fold") { + forAll { (eithert: EitherT[Id, String, Int], f: String => Long, g: Int => Long) => + eithert.fold(f, g) should === (eithert.value.fold(f, g)) + } + } + + test("valueOr with Id consistent with Either valueOr") { + forAll { (eithert: EitherT[Id, String, Int], f: String => Int) => + eithert.valueOr(f) should === (eithert.value.valueOr(f)) + } + } + + test("getOrElse with Id consistent with Either getOrElse") { + forAll { (eithert: EitherT[Id, String, Int], i: Int) => + eithert.getOrElse(i) should === (eithert.value.getOrElse(i)) + } + } + + test("getOrElseF with Id consistent with Either getOrElse") { + forAll { (eithert: EitherT[Id, String, Int], i: Int) => + eithert.getOrElseF(i) should === (eithert.value.getOrElse(i)) + } + } + + test("orElse with Id consistent with Either orElse") { + forAll { (eithert: EitherT[Id, String, Int], fallback: EitherT[Id, String, Int]) => + eithert.orElse(fallback).value should === (eithert.value.orElse(fallback.value)) + } + } + + test("orElse evaluates effect only once") { + forAll { (either: Either[String, Int], fallback: EitherT[Eval, String, Int]) => + var evals = 0 + val eithert = (EitherT(Eval.always { evals += 1; either }) orElse fallback) + eithert.value.value + evals should === (1) + } + } + + test("forall with Id consistent with Either forall") { + forAll { (eithert: EitherT[Id, String, Int], f: Int => Boolean) => + eithert.forall(f) should === (eithert.value.forall(f)) + } + } + + test("exists with Id consistent with Either exists") { + forAll { (eithert: EitherT[Id, String, Int], f: Int => Boolean) => + eithert.exists(f) should === (eithert.value.exists(f)) + } + } + + test("leftMap with Id consistent with Either leftMap") { + forAll { (eithert: EitherT[Id, String, Int], f: String => Long) => + eithert.leftMap(f).value should === (eithert.value.leftMap(f)) + } + } + + test("compare with Id consistent with Either compare") { + forAll { (x: EitherT[Id, String, Int], y: EitherT[Id, String, Int]) => + x.compare(y) should === (x.value.compare(y.value)) + } + } + + test("=== with Id consistent with Either ===") { + forAll { (x: EitherT[Id, String, Int], y: EitherT[Id, String, Int]) => + x === y should === (x.value === y.value) + } + } + + test("traverse with Id consistent with Either traverse") { + forAll { (x: EitherT[Id, String, Int], f: Int => Option[Long]) => + val e: Either[String, Int] = x.value + x.traverse(f).map(_.value) should === (e.traverse(f)) + } + } + + test("foldLeft with Id consistent with Either foldLeft") { + forAll { (x: EitherT[Id, String, Int], l: Long, f: (Long, Int) => Long) => + x.foldLeft(l)(f) should === (x.value.foldLeft(l)(f)) + } + } + + test("foldRight with Id consistent with Either foldRight") { + forAll { (x: EitherT[Id, String, Int], l: Eval[Long], f: (Int, Eval[Long]) => Eval[Long]) => + x.foldRight(l)(f) should === (x.value.foldRight(l)(f)) + } + } + + test("merge with Id consistent with Either merge") { + forAll { (x: EitherT[Id, Int, Int]) => + x.merge should === (x.value.merge) + } + } + + test("to consistent with toOption") { + forAll { (x: EitherT[List, String, Int]) => + x.to[Option] should === (x.toOption.value) + } + } + + test("toEither consistent with toOption") { + forAll { (x: EitherT[List, String, Int]) => + x.value.map(_.right.toOption) should === (x.toOption.value) + } + } + + test("ensure on left is identity") { + forAll { (x: EitherT[Id, String, Int], s: String, p: Int => Boolean) => + if (x.isLeft) { + x.ensure(s)(p) should === (x) + } + } + } + + test("ensure on right is identity if predicate satisfied") { + forAll { (x: EitherT[Id, String, Int], s: String, p: Int => Boolean) => + if (x.isRight && p(x getOrElse 0)) { + x.ensure(s)(p) should === (x) + } + } + } + + 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)) + } + } + } +} diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 97d7d74e08..8e4641e911 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -1,18 +1,23 @@ package cats package tests -import cats.laws.discipline.{BitraverseTests, TraverseTests, MonadTests, SerializableTests, CartesianTests} -import cats.kernel.laws.OrderLaws +import cats.data.EitherT +import cats.laws.discipline._ +import cats.kernel.laws.{GroupLaws, OrderLaws} +import scala.util.Try class EitherTests extends CatsSuite { - implicit val iso = CartesianTests.Isomorphisms.invariant[Either[Int, ?]] + checkAll("Either[String, Int]", GroupLaws[Either[String, Int]].monoid) + checkAll("Either[Int, Int]", CartesianTests[Either[Int, ?]].cartesian[Int, Int, Int]) checkAll("Cartesian[Either[Int, ?]]", SerializableTests.serializable(Cartesian[Either[Int, ?]])) - checkAll("Either[Int, Int]", MonadTests[Either[Int, ?]].monad[Int, Int, Int]) - checkAll("Monad[Either[Int, ?]]", SerializableTests.serializable(Monad[Either[Int, ?]])) + implicit val eq0 = EitherT.catsDataEqForEitherT[Either[Int, ?], Int, Int] + + checkAll("Either[Int, Int]", MonadErrorTests[Either[Int, ?], Int].monadError[Int, Int, Int]) + checkAll("MonadError[Either[Int, ?]]", SerializableTests.serializable(MonadError[Either[Int, ?], Int])) checkAll("Either[Int, Int] with Option", TraverseTests[Either[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Either[Int, ?]", SerializableTests.serializable(Traverse[Either[Int, ?]])) @@ -20,6 +25,9 @@ class EitherTests extends CatsSuite { checkAll("Either[?, ?]", BitraverseTests[Either].bitraverse[Option, Int, Int, Int, String, String, String]) checkAll("Bitraverse[Either]", SerializableTests.serializable(Bitraverse[Either])) + checkAll("Either[ListWrapper[String], ?]", SemigroupKTests[Either[ListWrapper[String], ?]].semigroupK[Int]) + checkAll("SemigroupK[Either[ListWrapper[String], ?]]", SerializableTests.serializable(SemigroupK[Either[ListWrapper[String], ?]])) + val partialOrder = catsStdPartialOrderForEither[Int, String] val order = implicitly[Order[Either[Int, String]]] val monad = implicitly[Monad[Either[Int, ?]]] @@ -55,4 +63,177 @@ class EitherTests extends CatsSuite { val x: Either[String, Int] = Left("l") x.map2Eval(bomb)(_ + _).value should === (x) } + + test("catchOnly lets non-matching exceptions escape") { + val _ = intercept[NumberFormatException] { + Either.catchOnly[IndexOutOfBoundsException]{ "foo".toInt } + } + } + + test("catchNonFatal catches non-fatal exceptions") { + assert(Either.catchNonFatal{ "foo".toInt }.isLeft) + assert(Either.catchNonFatal{ throw new Throwable("blargh") }.isLeft) + } + + test("fromTry is left for failed Try") { + forAll { t: Try[Int] => + t.isFailure should === (Either.fromTry(t).isLeft) + } + } + + test("fromOption isLeft consistent with Option.isEmpty") { + forAll { (o: Option[Int], s: String) => + Either.fromOption(o, s).isLeft should === (o.isEmpty) + } + } + + test("double swap is identity") { + forAll { (x: Either[Int, String]) => + x.swap.swap should === (x) + } + } + + test("swap negates isLeft/isRight") { + forAll { (x: Either[Int, String]) => + x.isLeft should !== (x.swap.isLeft) + x.isRight should !== (x.swap.isRight) + } + } + + test("isLeft consistent with isRight") { + forAll { (x: Either[Int, String]) => + x.isLeft should !== (x.isRight) + } + } + + test("foreach is noop for left") { + forAll { (x: Either[Int, String]) => + var count = 0 + x.foreach{ _ => count += 1} + (count == 0) should === (x.isLeft) + } + } + + test("getOrElse ignores default for right") { + forAll { (x: Either[Int, String], s: String, t: String) => + if (x.isRight) { + x.getOrElse(s) should === (x.getOrElse(t)) + } + } + } + + test("orElse") { + forAll { (x: Either[Int, String], y: Either[Int, String]) => + val z = x.orElse(y) + (z === (x)) || (z === (y)) should === (true) + } + } + + test("recover recovers handled values") { + val either = Either.left[String, Int]("either") + either.recover { case "either" => 5 }.isRight should === (true) + } + + test("recover ignores unhandled values") { + val either = Either.left[String, Int]("either") + either.recover { case "noteither" => 5 } should === (either) + } + + test("recover ignores the right side") { + val either = Either.right[String, Int](10) + either.recover { case "either" => 5 } should === (either) + } + + test("recoverWith recovers handled values") { + val either = Either.left[String, Int]("either") + either.recoverWith { case "either" => Either.right[String, Int](5) }.isRight should === (true) + } + + test("recoverWith ignores unhandled values") { + val either = Either.left[String, Int]("either") + either.recoverWith { case "noteither" => Either.right[String, Int](5) } should === (either) + } + + test("recoverWith ignores the right side") { + val either = Either.right[String, Int](10) + either.recoverWith { case "either" => Either.right[String, Int](5) } should === (either) + } + + test("valueOr consistent with swap then map then merge") { + forAll { (x: Either[Int, String], f: Int => String) => + x.valueOr(f) should === (x.swap.map(f).merge) + } + } + + test("isLeft implies forall") { + forAll { (x: Either[Int, String], p: String => Boolean) => + if (x.isLeft) { + x.forall(p) should === (true) + } + } + } + + test("isLeft implies exists is false") { + forAll { (x: Either[Int, String], p: String => Boolean) => + if (x.isLeft) { + x.exists(p) should === (false) + } + } + } + + test("ensure on left is identity") { + forAll { (x: Either[Int, String], i: Int, p: String => Boolean) => + if (x.isLeft) { + x.ensure(i)(p) should === (x) + } + } + } + + test("toIor then toEither is identity") { + forAll { (x: Either[Int, String]) => + x.toIor.toEither should === (x) + } + } + + test("toTry then fromTry is identity") { + implicit def eqTh: Eq[Throwable] = Eq.allEqual + + forAll { (x: Throwable Either String) => + Either.fromTry(x.toTry) should === (x) + } + } + + test("isLeft consistency") { + forAll { (x: Either[Int, String]) => + x.isLeft should === (x.toOption.isEmpty) + x.isLeft should === (x.toList.isEmpty) + x.isLeft should === (x.toValidated.isInvalid) + x.isLeft should === (x.toValidatedNel.isInvalid) + Option(x.isLeft) should === (x.toEitherT[Option].isLeft) + } + } + + test("withValidated") { + forAll { (x: Either[Int, String], f: Int => Double) => + x.withValidated(_.bimap(f, identity)) should === (x.leftMap(f)) + } + } + + test("combine is right iff both operands are right") { + forAll { (x: Either[Int, String], y: Either[Int, String]) => + x.combine(y).isRight should === (x.isRight && y.isRight) + } + } + + test("to consistent with toList") { + forAll { (x: Either[Int, String]) => + x.to[List, String] should === (x.toList) + } + } + + test("to consistent with toOption") { + forAll { (x: Either[Int, String]) => + x.to[Option, String] should === (x.toOption) + } + } } diff --git a/tests/src/test/scala/cats/tests/MonadCombineTests.scala b/tests/src/test/scala/cats/tests/MonadCombineTests.scala index 6a4901b10a..c9f11e234e 100644 --- a/tests/src/test/scala/cats/tests/MonadCombineTests.scala +++ b/tests/src/test/scala/cats/tests/MonadCombineTests.scala @@ -1,14 +1,11 @@ package cats package tests -import cats.data.Xor -import cats.laws.discipline.arbitrary.catsLawsArbitraryForXor - class MonadCombineTest extends CatsSuite { test("separate") { - forAll { (list: List[Xor[Int, String]]) => - val ints = list.collect { case Xor.Left(i) => i } - val strings = list.collect { case Xor.Right(s) => s } + forAll { (list: List[Either[Int, String]]) => + val ints = list.collect { case Left(i) => i } + val strings = list.collect { case Right(s) => s } val expected = (ints, strings) MonadCombine[List].separate(list) should === (expected) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 0b2a82789a..041d72d0e0 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -256,8 +256,8 @@ class OptionTTests extends CatsSuite { } test("show") { - val xor: Either[String, Option[Int]] = Either.right(Some(1)) - OptionT[Either[String, ?], Int](xor).show should === ("Right(Some(1))") + val either: Either[String, Option[Int]] = Either.right(Some(1)) + OptionT[Either[String, ?], Int](either).show should === ("Right(Some(1))") } test("none") { diff --git a/tests/src/test/scala/cats/tests/RegressionTests.scala b/tests/src/test/scala/cats/tests/RegressionTests.scala index 3eed4329ec..50f000c87a 100644 --- a/tests/src/test/scala/cats/tests/RegressionTests.scala +++ b/tests/src/test/scala/cats/tests/RegressionTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{Const, NonEmptyList, Xor} +import cats.data.{Const, NonEmptyList} import scala.collection.mutable @@ -89,11 +89,11 @@ class RegressionTests extends CatsSuite { ) } - test("#513: traverse short circuits - Xor") { + test("#513: traverse short circuits - Either") { var count = 0 - def validate(i: Int): Xor[String, Int] = { + def validate(i: Int): Either[String, Int] = { count = count + 1 - if (i < 5) Xor.right(i) else Xor.left(s"$i is greater than 5") + if (i < 5) Either.right(i) else Either.left(s"$i is greater than 5") } def checkAndResetCount(expected: Int): Unit = { @@ -101,31 +101,31 @@ class RegressionTests extends CatsSuite { count = 0 } - List(1,2,6,8).traverseU(validate) should === (Xor.left("6 is greater than 5")) + List(1,2,6,8).traverseU(validate) should === (Either.left("6 is greater than 5")) // shouldn't have ever evaluted validate(8) checkAndResetCount(3) - Stream(1,2,6,8).traverseU(validate) should === (Xor.left("6 is greater than 5")) + Stream(1,2,6,8).traverseU(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(3) type StringMap[A] = Map[String, A] val intMap: StringMap[Int] = Map("one" -> 1, "two" -> 2, "six" -> 6, "eight" -> 8) - intMap.traverseU(validate) should === (Xor.left("6 is greater than 5")) + intMap.traverseU(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(3) - NonEmptyList.of(1,2,6,8).traverseU(validate) should === (Xor.left("6 is greater than 5")) + NonEmptyList.of(1,2,6,8).traverseU(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(3) - NonEmptyList.of(6,8).traverseU(validate) should === (Xor.left("6 is greater than 5")) + NonEmptyList.of(6,8).traverseU(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(1) - List(1,2,6,8).traverseU_(validate) should === (Xor.left("6 is greater than 5")) + List(1,2,6,8).traverseU_(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(3) - NonEmptyList.of(1,2,6,7,8).traverseU_(validate) should === (Xor.left("6 is greater than 5")) + NonEmptyList.of(1,2,6,7,8).traverseU_(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(3) - NonEmptyList.of(6,7,8).traverseU_(validate) should === (Xor.left("6 is greater than 5")) + NonEmptyList.of(6,7,8).traverseU_(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(1) } } diff --git a/tests/src/test/scala/cats/tests/TransLiftTests.scala b/tests/src/test/scala/cats/tests/TransLiftTests.scala index fbc354b7d7..befa32177c 100644 --- a/tests/src/test/scala/cats/tests/TransLiftTests.scala +++ b/tests/src/test/scala/cats/tests/TransLiftTests.scala @@ -1,7 +1,7 @@ package cats package tests -import data.{OptionT,XorT,WriterT,Kleisli, StateT} +import cats.data.{EitherT,OptionT,XorT,WriterT,Kleisli, StateT} class TransLiftTests extends CatsSuite { @@ -22,7 +22,8 @@ class TransLiftTests extends CatsSuite { override def map[A, B](fa: JustAp[A])(f: A => B): JustAp[B] = JustAp(f(fa.a)) } - test("transLift for XorT, OptionT, WriterT requires only Functor") { + test("transLift for EitherT, XorT, OptionT, WriterT requires only Functor") { + val e: EitherT[JustFunctor, Int, Int] = JustFunctor(1).liftT[λ[(α[_], β) => EitherT[α, Int, β]]] val d: XorT[JustFunctor, Int, Int] = JustFunctor(1).liftT[λ[(α[_], β) => XorT[α, Int, β]]] val c: OptionT[JustFunctor, Int] = JustFunctor(1).liftT[OptionT] val a: WriterT[JustFunctor, Int, Int] = JustFunctor(1).liftT[λ[(α[_], β) => WriterT[α, Int, β]]] diff --git a/tests/src/test/scala/cats/tests/UnapplyTests.scala b/tests/src/test/scala/cats/tests/UnapplyTests.scala index 6eccf76e18..623dbd3381 100644 --- a/tests/src/test/scala/cats/tests/UnapplyTests.scala +++ b/tests/src/test/scala/cats/tests/UnapplyTests.scala @@ -14,8 +14,8 @@ class UnapplyTests extends CatsSuite { } test("Unapply works for F[_,_] with the left fixed") { - val x = Traverse[List].traverseU(List(1,2,3))(Xor.right(_)) - (x: String Xor List[Int]) should === (Xor.right(List(1,2,3))) + val x = Traverse[List].traverseU(List(1,2,3))(Either.right(_)) + (x: Either[String, List[Int]]) should === (Either.right(List(1,2,3))) } test("Unapply works for F[_[_],_] with the left fixed") { diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 646e6f83da..d40ee2cf6e 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{EitherT, NonEmptyList, Validated, ValidatedNel, Xor} +import cats.data.{EitherT, NonEmptyList, Validated, ValidatedNel} import cats.data.Validated.{Valid, Invalid} import cats.laws.discipline.{BitraverseTests, TraverseTests, ApplicativeErrorTests, SerializableTests, CartesianTests} import org.scalacheck.Arbitrary._ @@ -156,9 +156,9 @@ class ValidatedTests extends CatsSuite { (Validated.invalid("foo") andThen even) should === (Validated.invalid("foo")) } - test("fromOption consistent with Xor.fromOption"){ + test("fromOption consistent with Either.fromOption"){ forAll { (o: Option[Int], s: String) => - Validated.fromOption(o, s) should === (Xor.fromOption(o, s).toValidated) + Validated.fromOption(o, s) should === (Either.fromOption(o, s).toValidated) } } From 8fc564e25067e45f8255cf54854f5f356a462149 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 14 Aug 2016 20:50:45 -0700 Subject: [PATCH 08/17] ScalaStyle fixes --- core/src/main/scala/cats/instances/either.scala | 1 + core/src/main/scala/cats/syntax/either.scala | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index be73acc698..2058904920 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -26,6 +26,7 @@ trait EitherInstances extends EitherInstances1 { } } + // scalastyle:off method.length implicit def catsStdInstancesForEither[A]: MonadError[Either[A, ?], A] with Traverse[Either[A, ?]] with RecursiveTailRecM[Either[A, ?]] = new MonadError[Either[A, ?], A] with Traverse[Either[A, ?]] with RecursiveTailRecM[Either[A, ?]] { def pure[B](b: B): Either[A, B] = Right(b) diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index 782db63616..2d44f219b9 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -8,7 +8,7 @@ import scala.util.{Failure, Success, Try} trait EitherSyntax { implicit def catsSyntaxEither[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab) - implicit def catsSyntaxEitherObject(either: Either.type): EitherObjectOps = new EitherObjectOps(either) + implicit def catsSyntaxEitherObject(either: Either.type ): EitherObjectOps = new EitherObjectOps(either) } final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { @@ -191,7 +191,7 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { def toEitherT[F[_]: Applicative]: EitherT[F, A, B] = EitherT.fromEither(eab) } -final class EitherObjectOps(val either: Either.type) extends AnyVal { +final class EitherObjectOps(val either: Either.type ) extends AnyVal { def left[A, B](a: A): Either[A, B] = Left(a) def right[A, B](b: B): Either[A, B] = Right(b) From 4530d3aa93131ec28096968a3c903ed1016dbf1b Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 15 Aug 2016 13:29:51 -0700 Subject: [PATCH 09/17] Fix ScalaDoc, address comments - Remove variance workarounds since Either syntax is invariant - Prefer pattern matching over fold for performance - Fix ScalaDoc - Replace ad-hoc casts with leftCast and rightCast - Re-enable ScalaStyle after disables and disable ScalaStyle on Either.type enrichment --- .../main/scala/cats/ApplicativeError.scala | 2 +- core/src/main/scala/cats/FlatMap.scala | 2 +- core/src/main/scala/cats/data/EitherT.scala | 28 +-- core/src/main/scala/cats/data/Ior.scala | 2 +- .../main/scala/cats/instances/either.scala | 1 + core/src/main/scala/cats/syntax/either.scala | 195 ++++++++++++------ .../test/scala/cats/tests/EitherTests.scala | 4 +- 7 files changed, 153 insertions(+), 81 deletions(-) diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index ee1e473539..4f36625205 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -39,7 +39,7 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { /** * Handle errors by turning them into [[scala.util.Either]] values. * - * If there is no error, then an [[scala.util.Right]] value will be returned instead. + * If there is no error, then an `scala.util.Right` value will be returned instead. * * All non-fatal errors should be handled by this method. */ diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index 979dda9bac..a7767ee808 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -92,7 +92,7 @@ import simulacrum.typeclass flatMap(fa)(if (_) ifTrue else ifFalse) /** - * Keeps calling `f` until a `[[Right]][B]` is returned. + * Keeps calling `f` until a `scala.util.Right[B]` is returned. * * Based on Phil Freeman's * [[http://functorial.com/stack-safety-for-free/index.pdf Stack Safety for Free]]. diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 1e58910f61..c9649e926f 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -21,19 +21,19 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def swap(implicit F: Functor[F]): EitherT[F, B, A] = EitherT(F.map(value)(_.swap)) - def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = F.map(value)(_.getOrElse(default)) + def getOrElse(default: => B)(implicit F: Functor[F]): F[B] = F.map(value)(_.getOrElse(default)) - def getOrElseF[BB >: B](default: => F[BB])(implicit F: Monad[F]): F[BB] = { + def getOrElseF(default: => F[B])(implicit F: Monad[F]): F[B] = { F.flatMap(value) { case Left(_) => default case Right(b) => F.pure(b) } } - def orElse[AA, BB >: B](default: => EitherT[F, AA, BB])(implicit F: Monad[F]): EitherT[F, AA, BB] = { + def orElse(default: => EitherT[F, A, B])(implicit F: Monad[F]): EitherT[F, A, B] = { EitherT(F.flatMap(value) { case Left(_) => default.value - case r @ Right(_) => F.pure(r.asInstanceOf[Either[AA, BB]]) + case r @ Right(_) => F.pure(r) }) } @@ -46,21 +46,21 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { case other => F.pure(other) }) - def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = fold(f, identity) + def valueOr(f: A => B)(implicit F: Functor[F]): F[B] = fold(f, identity) def forall(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.forall(f)) def exists(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.exists(f)) - def ensure[AA >: A](onFailure: => AA)(f: B => Boolean)(implicit F: Functor[F]): EitherT[F, AA, B] = EitherT(F.map(value)(_.ensure(onFailure)(f))) + def ensure(onFailure: => A)(f: B => Boolean)(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(value)(_.ensure(onFailure)(f))) def toOption(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.toOption)) def to[G[_]](implicit F: Functor[F], G: Alternative[G]): F[G[B]] = - F.map(value)(_.to[G, B]) + F.map(value)(_.to[G]) def collectRight(implicit F: MonadCombine[F]): F[B] = - F.flatMap(value)(_.to[F, B]) + F.flatMap(value)(_.to[F]) def bimap[C, D](fa: A => C, fb: B => D)(implicit F: Functor[F]): EitherT[F, C, D] = EitherT(F.map(value)(_.bimap(fa, fb))) @@ -70,19 +70,19 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def applyAlt[D](ff: EitherT[F, A, B => D])(implicit F: Apply[F]): EitherT[F, A, D] = EitherT[F, A, D](F.map2(this.value, ff.value)((xb, xbd) => Apply[Either[A, ?]].ap(xbd)(xb))) - def flatMap[AA >: A, D](f: B => EitherT[F, AA, D])(implicit F: Monad[F]): EitherT[F, AA, D] = + def flatMap[D](f: B => EitherT[F, A, D])(implicit F: Monad[F]): EitherT[F, A, D] = EitherT(F.flatMap(value) { - case l @ Left(_) => F.pure(l.asInstanceOf[Either[AA, D]]) + case l @ Left(_) => F.pure((l: Either[A, B]).rightCast[D]) case Right(b) => f(b).value }) - def flatMapF[AA >: A, D](f: B => F[Either[AA, D]])(implicit F: Monad[F]): EitherT[F, AA, D] = + def flatMapF[D](f: B => F[Either[A, D]])(implicit F: Monad[F]): EitherT[F, A, D] = flatMap(f andThen EitherT.apply) def transform[C, D](f: Either[A, B] => Either[C, D])(implicit F: Functor[F]): EitherT[F, C, D] = EitherT(F.map(value)(f)) - def subflatMap[AA >: A, D](f: B => Either[AA, D])(implicit F: Functor[F]): EitherT[F, AA, D] = + def subflatMap[D](f: B => Either[A, D])(implicit F: Functor[F]): EitherT[F, A, D] = transform(_.flatMap(f)) def map[D](f: B => D)(implicit F: Functor[F]): EitherT[F, A, D] = bimap(identity, f) @@ -110,10 +110,10 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C])(implicit F: Foldable[F]): Eval[C] = F.foldRight(value, lc)((axb, lc) => axb.foldRight(lc)(f)) - def merge[AA >: A](implicit ev: B <:< AA, F: Functor[F]): F[AA] = F.map(value)(_.fold(identity, ev.apply)) + def merge(implicit ev: B <:< A, F: Functor[F]): F[A] = F.map(value)(_.fold(identity, ev.apply)) /** - * Similar to [[Either.combine]] but mapped over an `F` context. + * Similar to `Either#combine` but mapped over an `F` context. * * Examples: * {{{ diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index f504f78bfc..88801735e2 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -18,7 +18,7 @@ import scala.annotation.tailrec * * `A [[Ior]] B` is isomorphic to `Either[Either[A, B], (A, B)]`, but provides methods biased toward `B` * values, regardless of whether the `B` values appear in a [[Ior.Right Right]] or a [[Ior.Both Both]]. - * The isomorphic [[Either]] form can be accessed via the [[unwrap]] method. + * The isomorphic [[scala.util.Either]] form can be accessed via the [[unwrap]] method. */ sealed abstract class Ior[+A, +B] extends Product with Serializable { diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 2058904920..71cb64515b 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -81,6 +81,7 @@ trait EitherInstances extends EitherInstances1 { override def ensure[B](fab: Either[A, B])(error: => A)(predicate: B => Boolean): Either[A, B] = fab.ensure(error)(predicate) } + // scalastyle:on method.length implicit def catsStdOrderForEither[A, B](implicit A: Order[A], B: Order[B]): Order[Either[A, B]] = new Order[Either[A, B]] { def compare(x: Either[A, B], y: Either[A, B]): Int = x.fold( diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index 2d44f219b9..acaa79f81b 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -8,57 +8,94 @@ import scala.util.{Failure, Success, Try} trait EitherSyntax { implicit def catsSyntaxEither[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab) - implicit def catsSyntaxEitherObject(either: Either.type ): EitherObjectOps = new EitherObjectOps(either) + implicit def catsSyntaxEitherObject(either: Either.type): EitherObjectOps = new EitherObjectOps(either) // scalastyle:off ensure.single.space.after.token } final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { - def foreach(f: B => Unit): Unit = eab.fold(_ => (), f) + def foreach(f: B => Unit): Unit = eab match { + case Left(_) => () + case Right(b) => f(b) + } - def getOrElse[BB >: B](default: => BB): BB = eab.fold(_ => default, identity) + def getOrElse(default: => B): B = eab match { + case Left(_) => default + case Right(b) => b + } - def orElse[C, BB >: B](fallback: => Either[C, BB]): Either[C, BB]= eab match { - case Left(_) => fallback - case r @ Right(_) => r.asInstanceOf[Either[C, BB]] + def orElse[C](fallback: => Either[C, B]): Either[C, B] = eab match { + case Left(_) => fallback + case Right(_) => leftCast[C] } - def recover[BB >: B](pf: PartialFunction[A, BB]): Either[A, BB] = eab match { + def recover(pf: PartialFunction[A, B]): Either[A, B] = eab match { case Left(a) if pf.isDefinedAt(a) => Right(pf(a)) case _ => eab } - def recoverWith[AA >: A, BB >: B](pf: PartialFunction[A, Either[AA, BB]]): Either[AA, BB] = eab match { + def recoverWith(pf: PartialFunction[A, Either[A, B]]): Either[A, B] = eab match { case Left(a) if pf.isDefinedAt(a) => pf(a) case _ => eab } - def valueOr[BB >: B](f: A => BB): BB = eab.fold(f, identity) + def valueOr(f: A => B): B = eab match { + case Left(a) => f(a) + case Right(b) => b + } - def forall(f: B => Boolean): Boolean = eab.fold(_ => true, f) + def forall(f: B => Boolean): Boolean = eab match { + case Left(_) => true + case Right(b) => f(b) + } - def exists(f: B => Boolean): Boolean = eab.fold(_ => false, f) + def exists(f: B => Boolean): Boolean = eab match { + case Left(_) => false + case Right(b) => f(b) + } - def ensure[AA >: A](onFailure: => AA)(f: B => Boolean): Either[AA, B] = - eab.fold(_ => eab, b => if (f(b)) eab else Left(onFailure)) + def ensure(onFailure: => A)(f: B => Boolean): Either[A, B] = eab match { + case Left(_) => eab + case Right(b) => if (f(b)) eab else Left(onFailure) + } - def toIor: A Ior B = eab.fold(Ior.left, Ior.right) + def toIor: A Ior B = eab match { + case Left(a) => Ior.left(a) + case Right(b) => Ior.right(b) + } - def toOption: Option[B] = eab.fold(_ => None, Some(_)) + def toOption: Option[B] = eab match { + case Left(_) => None + case Right(b) => Some(b) + } - def toList: List[B] = eab.fold(_ => Nil, _ :: Nil) + def toList: List[B] = eab match { + case Left(_) => Nil + case Right(b) => List(b) + } - def toTry(implicit ev: A <:< Throwable): Try[B] = eab.fold(a => Failure(ev(a)), Success(_)) + def toTry(implicit ev: A <:< Throwable): Try[B] = eab match { + case Left(a) => Failure(ev(a)) + case Right(b) => Success(b) + } - def toValidated: Validated[A, B] = eab.fold(Validated.Invalid.apply, Validated.Valid.apply) + def toValidated: Validated[A, B] = eab match { + case Left(a) => Validated.invalid(a) + case Right(b) => Validated.valid(b) + } - /** Returns a [[ValidatedNel]] representation of this disjunction with the `Left` value - * as a single element on the `Invalid` side of the [[NonEmptyList]]. */ - def toValidatedNel[AA >: A]: ValidatedNel[AA, B] = eab.fold(Validated.invalidNel, Validated.valid) + /** Returns a [[cats.data.ValidatedNel]] representation of this disjunction with the `Left` value + * as a single element on the `Invalid` side of the [[cats.data.NonEmptyList]]. */ + def toValidatedNel: ValidatedNel[A, B] = eab match { + case Left(a) => Validated.invalidNel(a) + case Right(b) => Validated.valid(b) + } def withValidated[AA, BB](f: Validated[A, B] => Validated[AA, BB]): Either[AA, BB] = f(toValidated).toEither - def to[F[_], BB >: B](implicit F: Alternative[F]): F[BB] = - eab.fold(_ => F.empty, F.pure) + def to[F[_]](implicit F: Alternative[F]): F[B] = eab match { + case Left(_) => F.empty + case Right(b) => F.pure(b) + } def bimap[C, D](fa: A => C, fb: B => D): Either[C, D] = eab match { case Left(a) => Left(fa(a)) @@ -66,51 +103,79 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { } def map[C](f: B => C): Either[A, C] = eab match { - case l @ Left(_) => l.asInstanceOf[Either[A, C]] - case Right(b) => Right(f(b)) + case Left(_) => rightCast[C] + case Right(b) => Right(f(b)) } - def map2Eval[AA >: A, C, Z](fc: Eval[Either[AA, C]])(f: (B, C) => Z): Eval[Either[AA, Z]] = + def map2Eval[C, Z](fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = eab match { - case l @ Left(_) => Now(l.asInstanceOf[Either[AA, Z]]) - case Right(b) => fc.map(either => new EitherOps(either).map(f(b, _))) + case Left(_) => Now(rightCast[Z]) + case Right(b) => fc.map(either => new EitherOps(either).map(f(b, _))) } def leftMap[C](f: A => C): Either[C, B] = eab match { - case Left(a) => Left(f(a)) - case r @ Right(_) => r.asInstanceOf[Either[C, B]] + case Left(a) => Left(f(a)) + case Right(_) => leftCast[C] } - def flatMap[AA >: A, D](f: B => Either[AA, D]): Either[AA, D] = eab match { - case l @ Left(_) => l.asInstanceOf[Either[AA, D]] - case Right(b) => f(b) + def flatMap[D](f: B => Either[A, D]): Either[A, D] = eab match { + case Left(_) => rightCast[D] + case Right(b) => f(b) } - def compare[AA >: A, BB >: B](that: Either[AA, BB])(implicit AA: Order[AA], BB: Order[BB]): Int = eab.fold( - a => that.fold(AA.compare(a, _), _ => -1), - b => that.fold(_ => 1, BB.compare(b, _)) - ) + def compare(that: Either[A, B])(implicit A: Order[A], B: Order[B]): Int = eab match { + case Left(a1) => + that match { + case Left(a2) => A.compare(a1, a2) + case Right(_) => -1 + } + case Right(b1) => + that match { + case Left(_) => 1 + case Right(b2) => B.compare(b1, b2) + } + } - def partialCompare[AA >: A, BB >: B](that: Either[AA, BB])(implicit AA: PartialOrder[AA], BB: PartialOrder[BB]): Double = - eab.fold( - a => that.fold(AA.partialCompare(a, _), _ => -1), - b => that.fold(_ => 1, BB.partialCompare(b, _)) - ) + def partialCompare(that: Either[A, B])(implicit A: PartialOrder[A], B: PartialOrder[B]): Double = eab match { + case Left(a1) => + that match { + case Left(a2) => A.partialCompare(a1, a2) + case Right(_) => -1 + } + case Right(b1) => + that match { + case Left(_) => 1 + case Right(b2) => B.partialCompare(b1, b2) + } + } - def ===[AA >: A, BB >: B](that: Either[AA, BB])(implicit AA: Eq[AA], BB: Eq[BB]): Boolean = eab.fold( - a => that.fold(AA.eqv(a, _), _ => false), - b => that.fold(_ => false, BB.eqv(b, _)) - ) + def ===(that: Either[A, B])(implicit A: Eq[A], B: Eq[B]): Boolean = eab match { + case Left(a1) => + that match { + case Left(a2) => A.eqv(a1, a2) + case Right(_) => false + } + case Right(b1) => + that match { + case Left(_) => false + case Right(b2) => B.eqv(b1, b2) + } + } - def traverse[F[_], AA >: A, D](f: B => F[D])(implicit F: Applicative[F]): F[Either[AA, D]] = eab match { - case l @ Left(_) => F.pure(l.asInstanceOf[Either[AA, D]]) + def traverse[F[_], D](f: B => F[D])(implicit F: Applicative[F]): F[Either[A, D]] = eab match { + case Left(_) => F.pure(rightCast[D]) case Right(b) => F.map(f(b))(Right(_)) } - def foldLeft[C](c: C)(f: (C, B) => C): C = eab.fold(_ => c, f(c, _)) + def foldLeft[C](c: C)(f: (C, B) => C): C = eab match { + case Left(_) => c + case Right(b) => f(c, b) + } - def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = - eab.fold(_ => lc, b => f(b, lc)) + def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = eab match { + case Left(_) => lc + case Right(b) => f(b, lc) + } /** * Combine with another `Either` value. @@ -144,20 +209,20 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { * res3: Either[String, Int] = Right(7) * }}} */ - final def combine[AA >: A, BB >: B](that: Either[AA, BB])(implicit BB: Semigroup[BB]): Either[AA, BB] = eab match { + final def combine(that: Either[A, B])(implicit B: Semigroup[B]): Either[A, B] = eab match { case left @ Left(_) => left case Right(b1) => that match { case left @ Left(_) => left - case Right(b2) => Right(BB.combine(b1, b2)) + case Right(b2) => Right(B.combine(b1, b2)) } } - def show[AA >: A, BB >: B](implicit AA: Show[AA], BB: Show[BB]): String = eab.fold( - a => s"Left(${AA.show(a)})", - b => s"Right(${BB.show(b)})" - ) + def show(implicit A: Show[A], B: Show[B]): String = eab match { + case Left(a) => s"Left(${A.show(a)})" + case Right(b) => s"Right(${B.show(b)})" + } - def ap[AA >: A, BB >: B, C](that: Either[AA, BB => C]): Either[AA, C] = (new EitherOps(that)).flatMap(this.map) + def ap[C](that: Either[A, B => C]): Either[A, C] = (new EitherOps(that)).flatMap(this.map) /** * Convert a `scala.util.Either` into a [[cats.data.Xor]]. @@ -179,7 +244,7 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { def toXor: A Xor B = Xor.fromEither(eab) /** - * Transform the `Either` into a [[EitherT]] while lifting it into the specified Applicative. + * Transform the `Either` into a [[cats.data.EitherT]] while lifting it into the specified Applicative. * * {{{ * scala> import cats.implicits._ @@ -189,9 +254,13 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { * }}} */ def toEitherT[F[_]: Applicative]: EitherT[F, A, B] = EitherT.fromEither(eab) + + private[cats] def leftCast[C]: Either[C, B] = eab.asInstanceOf[Either[C, B]] + + private[cats] def rightCast[C]: Either[A, C] = eab.asInstanceOf[Either[A, C]] } -final class EitherObjectOps(val either: Either.type ) extends AnyVal { +final class EitherObjectOps(val either: Either.type) extends AnyVal { // scalastyle:off ensure.single.space.after.token def left[A, B](a: A): Either[A, B] = Left(a) def right[A, B](b: B): Either[A, B] = Right(b) @@ -230,8 +299,10 @@ final class EitherObjectOps(val either: Either.type ) extends AnyVal { * Converts an `Option[B]` to an `A Xor B`, where the provided `ifNone` values is returned on * the left of the `Xor` when the specified `Option` is `None`. */ - def fromOption[A, B](o: Option[B], ifNone: => A): Either[A, B] = - o.fold(left[A, B](ifNone))(right) + def fromOption[A, B](o: Option[B], ifNone: => A): Either[A, B] = o match { + case None => left[A, B](ifNone) + case Some(a) => right(a) + } } final class CatchOnlyPartiallyApplied[T] private[syntax] { diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 8e4641e911..3f91e21f1f 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -227,13 +227,13 @@ class EitherTests extends CatsSuite { test("to consistent with toList") { forAll { (x: Either[Int, String]) => - x.to[List, String] should === (x.toList) + x.to[List] should === (x.toList) } } test("to consistent with toOption") { forAll { (x: Either[Int, String]) => - x.to[Option, String] should === (x.toOption) + x.to[Option] should === (x.toOption) } } } From d4df094775642d569385461fd6f0008aefe27f5a Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 16 Aug 2016 11:47:44 -0700 Subject: [PATCH 10/17] Eval uses defaultTailRecM --- core/src/main/scala/cats/Eval.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/Eval.scala b/core/src/main/scala/cats/Eval.scala index 21243fd943..36348160fe 100644 --- a/core/src/main/scala/cats/Eval.scala +++ b/core/src/main/scala/cats/Eval.scala @@ -301,11 +301,7 @@ private[cats] trait EvalInstances extends EvalInstances0 { def flatMap[A, B](fa: Eval[A])(f: A => Eval[B]): Eval[B] = fa.flatMap(f) def extract[A](la: Eval[A]): A = la.value def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Later(f(fa)) - def tailRecM[A, B](a: A)(f: A => Eval[Either[A, B]]): Eval[B] = - f(a).flatMap(_ match { - case Left(a1) => tailRecM(a1)(f) // recursion OK here, since flatMap is lazy - case Right(b) => Eval.now(b) - }) + def tailRecM[A, B](a: A)(f: A => Eval[Either[A, B]]): Eval[B] = defaultTailRecM(a)(f) } implicit def catsOrderForEval[A: Order]: Order[Eval[A]] = From eedd057830717f87ccf24149a9bc1e15816e2579 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 16 Aug 2016 21:32:04 -0700 Subject: [PATCH 11/17] Safer Either casting --- core/src/main/scala/cats/data/EitherT.scala | 3 +- .../main/scala/cats/instances/either.scala | 6 ++-- core/src/main/scala/cats/syntax/either.scala | 36 ++++++++++--------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index c9649e926f..2bdc04655e 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -3,6 +3,7 @@ package data import cats.functor.Bifunctor import cats.instances.either._ +import cats.syntax.EitherUtil import cats.syntax.either._ /** @@ -72,7 +73,7 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def flatMap[D](f: B => EitherT[F, A, D])(implicit F: Monad[F]): EitherT[F, A, D] = EitherT(F.flatMap(value) { - case l @ Left(_) => F.pure((l: Either[A, B]).rightCast[D]) + case l @ Left(_) => F.pure(EitherUtil.leftCast(l)) case Right(b) => f(b).value }) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 71cb64515b..5acaa3795d 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -1,6 +1,7 @@ package cats package instances +import cats.syntax.EitherUtil import cats.syntax.either._ import scala.annotation.tailrec @@ -54,10 +55,7 @@ trait EitherInstances extends EitherInstances1 { override def map2Eval[B, C, Z](fb: Either[A, B], fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = fb match { - // This should be safe, but we are forced to use `asInstanceOf`, - // because `Left[+A, +B]` extends Either[A, B] instead of - // `Either[A, Nothing]` - case l @ Left(_) => Now(l.asInstanceOf[Either[A, Z]]) + case l @ Left(_) => Now(EitherUtil.leftCast(l)) case Right(b) => fc.map(_.right.map(f(b, _))) } diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index acaa79f81b..cbac3404b8 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -23,8 +23,8 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { } def orElse[C](fallback: => Either[C, B]): Either[C, B] = eab match { - case Left(_) => fallback - case Right(_) => leftCast[C] + case Left(_) => fallback + case r @ Right(_) => EitherUtil.rightCast(r) } def recover(pf: PartialFunction[A, B]): Either[A, B] = eab match { @@ -103,24 +103,24 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { } def map[C](f: B => C): Either[A, C] = eab match { - case Left(_) => rightCast[C] - case Right(b) => Right(f(b)) + case l @ Left(_) => EitherUtil.leftCast(l) + case Right(b) => Right(f(b)) } def map2Eval[C, Z](fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = eab match { - case Left(_) => Now(rightCast[Z]) - case Right(b) => fc.map(either => new EitherOps(either).map(f(b, _))) + case l @ Left(_) => Now(EitherUtil.leftCast(l)) + case Right(b) => fc.map(either => new EitherOps(either).map(f(b, _))) } def leftMap[C](f: A => C): Either[C, B] = eab match { - case Left(a) => Left(f(a)) - case Right(_) => leftCast[C] + case Left(a) => Left(f(a)) + case r @ Right(_) => EitherUtil.rightCast(r) } def flatMap[D](f: B => Either[A, D]): Either[A, D] = eab match { - case Left(_) => rightCast[D] - case Right(b) => f(b) + case l @ Left(_) => EitherUtil.leftCast(l) + case Right(b) => f(b) } def compare(that: Either[A, B])(implicit A: Order[A], B: Order[B]): Int = eab match { @@ -163,8 +163,8 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { } def traverse[F[_], D](f: B => F[D])(implicit F: Applicative[F]): F[Either[A, D]] = eab match { - case Left(_) => F.pure(rightCast[D]) - case Right(b) => F.map(f(b))(Right(_)) + case l @ Left(_) => F.pure(EitherUtil.leftCast(l)) + case Right(b) => F.map(f(b))(Right(_)) } def foldLeft[C](c: C)(f: (C, B) => C): C = eab match { @@ -254,10 +254,6 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { * }}} */ def toEitherT[F[_]: Applicative]: EitherT[F, A, B] = EitherT.fromEither(eab) - - private[cats] def leftCast[C]: Either[C, B] = eab.asInstanceOf[Either[C, B]] - - private[cats] def rightCast[C]: Either[A, C] = eab.asInstanceOf[Either[A, C]] } final class EitherObjectOps(val either: Either.type) extends AnyVal { // scalastyle:off ensure.single.space.after.token @@ -314,3 +310,11 @@ final class CatchOnlyPartiallyApplied[T] private[syntax] { Left(t.asInstanceOf[T]) } } + +private[cats] object EitherUtil { + /** Cast the *right* type parameter of a `Left`. */ + def leftCast[A, B, C](l: Left[A, B]): Either[A, C] = l.asInstanceOf[Left[A, C]] + + /** Cast the *left* type parameter of a `Right` */ + def rightCast[A, B, C](r: Right[A, B]): Either[C, B] = r.asInstanceOf[Right[C, B]] +} From ba1a7860a34829755fd5adc9d0397c12adb8b2eb Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Tue, 16 Aug 2016 22:02:26 -0700 Subject: [PATCH 12/17] Expose Left/Right cast syntax - casting naming indicates which type parameter to cast --- core/src/main/scala/cats/data/EitherT.scala | 3 +- .../main/scala/cats/instances/either.scala | 2 +- core/src/main/scala/cats/syntax/either.scala | 33 +++++++++++++------ .../test/scala/cats/tests/EitherTests.scala | 12 +++++++ 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 2bdc04655e..66915a7404 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -3,7 +3,6 @@ package data import cats.functor.Bifunctor import cats.instances.either._ -import cats.syntax.EitherUtil import cats.syntax.either._ /** @@ -73,7 +72,7 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def flatMap[D](f: B => EitherT[F, A, D])(implicit F: Monad[F]): EitherT[F, A, D] = EitherT(F.flatMap(value) { - case l @ Left(_) => F.pure(EitherUtil.leftCast(l)) + case l @ Left(_) => F.pure(l.rightCast) case Right(b) => f(b).value }) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 5acaa3795d..d5b3cd628f 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -55,7 +55,7 @@ trait EitherInstances extends EitherInstances1 { override def map2Eval[B, C, Z](fb: Either[A, B], fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = fb match { - case l @ Left(_) => Now(EitherUtil.leftCast(l)) + case l @ Left(_) => Now(EitherUtil.rightCast(l)) case Right(b) => fc.map(_.right.map(f(b, _))) } diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index cbac3404b8..f4809f410a 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -9,6 +9,10 @@ trait EitherSyntax { implicit def catsSyntaxEither[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab) implicit def catsSyntaxEitherObject(either: Either.type): EitherObjectOps = new EitherObjectOps(either) // scalastyle:off ensure.single.space.after.token + + implicit def catsSyntaxLeft[A, B](left: Left[A, B]): LeftOps[A, B] = new LeftOps(left) + + implicit def catsSyntaxRight[A, B](right: Right[A, B]): RightOps[A, B] = new RightOps(right) } final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { @@ -24,7 +28,7 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { def orElse[C](fallback: => Either[C, B]): Either[C, B] = eab match { case Left(_) => fallback - case r @ Right(_) => EitherUtil.rightCast(r) + case r @ Right(_) => EitherUtil.leftCast(r) } def recover(pf: PartialFunction[A, B]): Either[A, B] = eab match { @@ -103,23 +107,23 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { } def map[C](f: B => C): Either[A, C] = eab match { - case l @ Left(_) => EitherUtil.leftCast(l) + case l @ Left(_) => EitherUtil.rightCast(l) case Right(b) => Right(f(b)) } def map2Eval[C, Z](fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = eab match { - case l @ Left(_) => Now(EitherUtil.leftCast(l)) + case l @ Left(_) => Now(EitherUtil.rightCast(l)) case Right(b) => fc.map(either => new EitherOps(either).map(f(b, _))) } def leftMap[C](f: A => C): Either[C, B] = eab match { case Left(a) => Left(f(a)) - case r @ Right(_) => EitherUtil.rightCast(r) + case r @ Right(_) => EitherUtil.leftCast(r) } def flatMap[D](f: B => Either[A, D]): Either[A, D] = eab match { - case l @ Left(_) => EitherUtil.leftCast(l) + case l @ Left(_) => EitherUtil.rightCast(l) case Right(b) => f(b) } @@ -163,7 +167,7 @@ final class EitherOps[A, B](val eab: Either[A, B]) extends AnyVal { } def traverse[F[_], D](f: B => F[D])(implicit F: Applicative[F]): F[Either[A, D]] = eab match { - case l @ Left(_) => F.pure(EitherUtil.leftCast(l)) + case l @ Left(_) => F.pure(EitherUtil.rightCast(l)) case Right(b) => F.map(f(b))(Right(_)) } @@ -311,10 +315,19 @@ final class CatchOnlyPartiallyApplied[T] private[syntax] { } } +final class LeftOps[A, B](val left: Left[A, B]) extends AnyVal { + /** Cast the right type parameter of the `Left`. */ + def rightCast[C]: Either[A, C] = left.asInstanceOf[Either[A, C]] +} + +final class RightOps[A, B](val right: Right[A, B]) extends AnyVal { + /** Cast the left type parameter of the `Right`. */ + def leftCast[C]: Either[C, B] = right.asInstanceOf[Either[C, B]] +} + +/** Convenience methods to use `Either` syntax inside `Either` syntax definitions. */ private[cats] object EitherUtil { - /** Cast the *right* type parameter of a `Left`. */ - def leftCast[A, B, C](l: Left[A, B]): Either[A, C] = l.asInstanceOf[Left[A, C]] + def leftCast[A, B, C](r: Right[A, B]): Either[C, B] = new RightOps(r).leftCast[C] - /** Cast the *left* type parameter of a `Right` */ - def rightCast[A, B, C](r: Right[A, B]): Either[C, B] = r.asInstanceOf[Right[C, B]] + def rightCast[A, B, C](l: Left[A, B]): Either[A, C] = new LeftOps(l).rightCast[C] } diff --git a/tests/src/test/scala/cats/tests/EitherTests.scala b/tests/src/test/scala/cats/tests/EitherTests.scala index 3f91e21f1f..5db908b543 100644 --- a/tests/src/test/scala/cats/tests/EitherTests.scala +++ b/tests/src/test/scala/cats/tests/EitherTests.scala @@ -44,6 +44,18 @@ class EitherTests extends CatsSuite { checkAll("Either[Int, String]", orderLaws.partialOrder(partialOrder)) checkAll("Either[Int, String]", orderLaws.order(order)) + test("Left/Right cast syntax") { + forAll { (e: Either[Int, String]) => + e match { + case l @ Left(_) => + l.rightCast[Double]: Either[Int, Double] + assert(true) + case r @ Right(_) => + r.leftCast[List[Byte]]: Either[List[Byte], String] + assert(true) + } + } + } test("implicit instances resolve specifically") { val eq = catsStdEqForEither[Int, String] From 76c0d29a388933cd34bb45060769aa7664ffbb53 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 19 Aug 2016 15:03:34 -0400 Subject: [PATCH 13/17] Drastically speed up Monoid[Map[K, V]] instance. This commit fixes a number of problems I introduced with cats-kernel. 1. Map's monoid was not overriding combineAll 2. It was also doing the wrong thing for combine 3. Monoid should have overridden combineAllOption The performance for map's `combine` is 150x faster after this commit, and `combineAll` is 780x faster. Rather than focusing on how good these speed ups are, I want to apologize for how bad the implementations were previously. The new `combine` is competitive with other implementations, and `combineAll` is about x5 faster than the equivalent code using combine. (All figures using before/after numbers for the given benchmark.) Thanks to Adelbert and Travis for noticing this problem. Also thanks to Travis for producing this benchmark! Finally, thanks to Oscar for helping me look at and fix this issue. --- .../scala/cats/bench/MapMonoidBench.scala | 59 +++++++++++++++++++ build.sbt | 2 + .../src/main/scala/cats/kernel/Monoid.scala | 3 + .../{Util.scala => StaticMethods.scala} | 2 +- .../scala/cats/kernel/instances/map.scala | 23 +++++++- .../scala/cats/kernel/instances/stream.scala | 2 - .../scala/cats/kernel/instances/vector.scala | 2 - 7 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 bench/src/main/scala/cats/bench/MapMonoidBench.scala rename kernel/src/main/scala/cats/kernel/instances/{Util.scala => StaticMethods.scala} (99%) diff --git a/bench/src/main/scala/cats/bench/MapMonoidBench.scala b/bench/src/main/scala/cats/bench/MapMonoidBench.scala new file mode 100644 index 0000000000..267be0887c --- /dev/null +++ b/bench/src/main/scala/cats/bench/MapMonoidBench.scala @@ -0,0 +1,59 @@ +package cats.bench + +//import cats.instances.list._ +import cats.instances.int._ +import cats.instances.map._ + +import scalaz.std.anyVal._ +//import scalaz.std.list._ +import scalaz.std.map._ + +import org.openjdk.jmh.annotations.{ Benchmark, Scope, State } + +@State(Scope.Benchmark) +class MapMonoidBench { + + val words: List[String] = + for { + c <- List("a", "b", "c", "d", "e") + t <- 1 to 100 + } yield c * t + + val maps: List[Map[String, Int]] = (words ++ words).map(s => Map(s -> 1)) + + @Benchmark def combineAllCats: Map[String, Int] = + cats.Monoid[Map[String, Int]].combineAll(maps) + + @Benchmark def combineCats: Map[String, Int] = + maps.foldLeft(Map.empty[String, Int]) { + case (acc, m) => cats.Monoid[Map[String, Int]].combine(acc, m) + } + + @Benchmark def combineScalaz: Map[String, Int] = + maps.foldLeft(Map.empty[String, Int]) { + case (acc, m) => scalaz.Monoid[Map[String, Int]].append(acc, m) + } + + @Benchmark def combineDirect: Map[String, Int] = + maps.foldLeft(Map.empty[String, Int]) { + case (acc, m) => m.foldLeft(acc) { + case (m, (k, v)) => m.updated(k, v + m.getOrElse(k, 0)) + } + } + + @Benchmark def combineGeneric: Map[String, Int] = + combineMapsGeneric[String, Int](maps, 0, _ + _) + + def combineMapsGeneric[K, V](maps: List[Map[K, V]], z: V, f: (V, V) => V): Map[K, V] = + maps.foldLeft(Map.empty[K, V]) { + case (acc, m) => m.foldLeft(acc) { + case (m, (k, v)) => m.updated(k, f(v, m.getOrElse(k, z))) + } + } + + @Benchmark def foldMapCats: Map[String, Int] = + cats.Foldable[List].foldMap(maps)(identity) + + @Benchmark def foldMapScalaz: Map[String, Int] = + scalaz.Foldable[List].foldMap(maps)(identity) +} diff --git a/build.sbt b/build.sbt index 9a45ec685f..8080755ac7 100644 --- a/build.sbt +++ b/build.sbt @@ -281,6 +281,8 @@ lazy val bench = project.dependsOn(macrosJVM, coreJVM, freeJVM, lawsJVM) .settings(catsSettings) .settings(noPublishSettings) .settings(commonJvmSettings) + .settings(libraryDependencies ++= Seq( + "org.scalaz" %% "scalaz-core" % "7.2.5")) .enablePlugins(JmhPlugin) // cats-js is JS-only diff --git a/kernel/src/main/scala/cats/kernel/Monoid.scala b/kernel/src/main/scala/cats/kernel/Monoid.scala index 0f3b58709f..73041e0d99 100644 --- a/kernel/src/main/scala/cats/kernel/Monoid.scala +++ b/kernel/src/main/scala/cats/kernel/Monoid.scala @@ -34,6 +34,9 @@ trait Monoid[@sp(Int, Long, Float, Double) A] extends Any with Semigroup[A] { */ def combineAll(as: TraversableOnce[A]): A = as.foldLeft(empty)(combine) + + override def combineAllOption(as: TraversableOnce[A]): Option[A] = + if (as.isEmpty) None else Some(combineAll(as)) } abstract class MonoidFunctions[M[T] <: Monoid[T]] extends SemigroupFunctions[M] { diff --git a/kernel/src/main/scala/cats/kernel/instances/Util.scala b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala similarity index 99% rename from kernel/src/main/scala/cats/kernel/instances/Util.scala rename to kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala index 0d48b7d193..da8f28ea34 100644 --- a/kernel/src/main/scala/cats/kernel/instances/Util.scala +++ b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala @@ -1,5 +1,5 @@ package cats.kernel -package instances.util +package instances import scala.collection.mutable diff --git a/kernel/src/main/scala/cats/kernel/instances/map.scala b/kernel/src/main/scala/cats/kernel/instances/map.scala index 9cee3e655c..f4b6201b8a 100644 --- a/kernel/src/main/scala/cats/kernel/instances/map.scala +++ b/kernel/src/main/scala/cats/kernel/instances/map.scala @@ -1,7 +1,7 @@ package cats.kernel package instances -import cats.kernel.instances.util.StaticMethods.addMap +import scala.collection.mutable package object map extends MapInstances @@ -24,12 +24,29 @@ class MapEq[K, V](implicit V: Eq[V]) extends Eq[Map[K, V]] { } class MapMonoid[K, V](implicit V: Semigroup[V]) extends Monoid[Map[K, V]] { + def empty: Map[K, V] = Map.empty def combine(xs: Map[K, V], ys: Map[K, V]): Map[K, V] = if (xs.size <= ys.size) { - addMap(xs, ys)((x, y) => V.combine(x, y)) + xs.foldLeft(ys) { case (my, (k, x)) => + my.updated(k, Semigroup.maybeCombine(x, my.get(k))) + } } else { - addMap(ys, xs)((y, x) => V.combine(x, y)) + ys.foldLeft(xs) { case (mx, (k, y)) => + mx.updated(k, Semigroup.maybeCombine(mx.get(k), y)) + } + } + + override def combineAll(xss: TraversableOnce[Map[K, V]]): Map[K, V] = { + val acc = mutable.Map.empty[K, V] + xss.foreach { m => + val it = m.iterator + while (it.hasNext) { + val (k, v) = it.next + m.updated(k, Semigroup.maybeCombine(m.get(k), v)) + } } + StaticMethods.wrapMutableMap(acc) + } } diff --git a/kernel/src/main/scala/cats/kernel/instances/stream.scala b/kernel/src/main/scala/cats/kernel/instances/stream.scala index 6fe7215601..027ea4795c 100644 --- a/kernel/src/main/scala/cats/kernel/instances/stream.scala +++ b/kernel/src/main/scala/cats/kernel/instances/stream.scala @@ -1,8 +1,6 @@ package cats.kernel package instances -import cats.kernel.instances.util.StaticMethods - package object stream extends StreamInstances trait StreamInstances extends StreamInstances1 { diff --git a/kernel/src/main/scala/cats/kernel/instances/vector.scala b/kernel/src/main/scala/cats/kernel/instances/vector.scala index 7343cdd7f3..128bbebe16 100644 --- a/kernel/src/main/scala/cats/kernel/instances/vector.scala +++ b/kernel/src/main/scala/cats/kernel/instances/vector.scala @@ -1,8 +1,6 @@ package cats.kernel package instances -import cats.kernel.instances.util.StaticMethods - package object vector extends VectorInstances trait VectorInstances extends VectorInstances1 { From 9ddd789cb679a9b1a0828be9c04fc9afce51a716 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 19 Aug 2016 15:10:38 -0400 Subject: [PATCH 14/17] Fix compile error in benchmark. --- bench/src/main/scala/cats/bench/MapMonoidBench.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/src/main/scala/cats/bench/MapMonoidBench.scala b/bench/src/main/scala/cats/bench/MapMonoidBench.scala index 267be0887c..f13d9c4a44 100644 --- a/bench/src/main/scala/cats/bench/MapMonoidBench.scala +++ b/bench/src/main/scala/cats/bench/MapMonoidBench.scala @@ -1,11 +1,11 @@ package cats.bench -//import cats.instances.list._ +import cats.instances.list._ import cats.instances.int._ import cats.instances.map._ import scalaz.std.anyVal._ -//import scalaz.std.list._ +import scalaz.std.list._ import scalaz.std.map._ import org.openjdk.jmh.annotations.{ Benchmark, Scope, State } From 3f75b3055f24de0bc73ffdbf069622818c546b5c Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 19 Aug 2016 15:16:22 -0400 Subject: [PATCH 15/17] Remove dead code. --- .../cats/kernel/instances/StaticMethods.scala | 22 ------------------- .../scala/cats/kernel/instances/map.scala | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala index da8f28ea34..8c3d1e15f8 100644 --- a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala +++ b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala @@ -5,15 +5,6 @@ import scala.collection.mutable object StaticMethods { - def initMutableMap[K, V](m: Map[K, V]): mutable.Map[K, V] = { - val result = mutable.Map.empty[K, V] - m.foreach { case (k, v) => result(k) = v } - result - } - - def wrapMutableMap[K, V](m: mutable.Map[K, V]): Map[K, V] = - new WrappedMutableMap(m) - private[kernel] class WrappedMutableMap[K, V](m: mutable.Map[K, V]) extends Map[K, V] { override def size: Int = m.size def get(k: K): Option[V] = m.get(k) @@ -22,19 +13,6 @@ object StaticMethods { def -(key: K): Map[K, V] = m.toMap - key } - // the caller should arrange so that the smaller map is the first - // argument, and the larger map is the second. - def addMap[K, V](small: Map[K, V], big: Map[K, V])(f: (V, V) => V): Map[K, V] = { - val m = initMutableMap(big) - small.foreach { case (k, v1) => - m(k) = m.get(k) match { - case Some(v2) => f(v1, v2) - case None => v1 - } - } - wrapMutableMap(m) - } - // scalastyle:off return def iteratorCompare[A](xs: Iterator[A], ys: Iterator[A])(implicit ev: Order[A]): Int = { while (true) { diff --git a/kernel/src/main/scala/cats/kernel/instances/map.scala b/kernel/src/main/scala/cats/kernel/instances/map.scala index f4b6201b8a..4311b9bcce 100644 --- a/kernel/src/main/scala/cats/kernel/instances/map.scala +++ b/kernel/src/main/scala/cats/kernel/instances/map.scala @@ -47,6 +47,6 @@ class MapMonoid[K, V](implicit V: Semigroup[V]) extends Monoid[Map[K, V]] { m.updated(k, Semigroup.maybeCombine(m.get(k), v)) } } - StaticMethods.wrapMutableMap(acc) + new StaticMethods.WrappedMutableMap(acc) } } From 6043c7f6787c12eb752d905e61c25f6ef34809a5 Mon Sep 17 00:00:00 2001 From: Erik Osheim Date: Fri, 19 Aug 2016 15:31:04 -0400 Subject: [PATCH 16/17] Readd StaticMethods.wrapMutableMap to allow outside reuse. Since we made WrappedMutableMap private to the kernel, we need something like this so that Algebra, Spire, Algebird, etc. can all take advantage of this basic pattern. --- .../src/main/scala/cats/kernel/instances/StaticMethods.scala | 3 +++ kernel/src/main/scala/cats/kernel/instances/map.scala | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala index 8c3d1e15f8..66f4cd3af6 100644 --- a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala +++ b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala @@ -5,6 +5,9 @@ import scala.collection.mutable object StaticMethods { + def wrapMutableMap[K, V](m: mutable.Map[K, V]): Map[K, V] = + new WrappedMutableMap(m) + private[kernel] class WrappedMutableMap[K, V](m: mutable.Map[K, V]) extends Map[K, V] { override def size: Int = m.size def get(k: K): Option[V] = m.get(k) diff --git a/kernel/src/main/scala/cats/kernel/instances/map.scala b/kernel/src/main/scala/cats/kernel/instances/map.scala index 4311b9bcce..f4b6201b8a 100644 --- a/kernel/src/main/scala/cats/kernel/instances/map.scala +++ b/kernel/src/main/scala/cats/kernel/instances/map.scala @@ -47,6 +47,6 @@ class MapMonoid[K, V](implicit V: Semigroup[V]) extends Monoid[Map[K, V]] { m.updated(k, Semigroup.maybeCombine(m.get(k), v)) } } - new StaticMethods.WrappedMutableMap(acc) + StaticMethods.wrapMutableMap(acc) } } From d75686d25fb5cd7e5572d9ca55716f3f49b31bf8 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Fri, 19 Aug 2016 19:59:45 -0700 Subject: [PATCH 17/17] s/Xor/Either in FreeT --- free/src/main/scala/cats/free/FreeT.scala | 64 +++++++++---------- .../src/test/scala/cats/free/FreeTTests.scala | 10 +-- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/free/src/main/scala/cats/free/FreeT.scala b/free/src/main/scala/cats/free/FreeT.scala index 2ef6091c10..558b58e740 100644 --- a/free/src/main/scala/cats/free/FreeT.scala +++ b/free/src/main/scala/cats/free/FreeT.scala @@ -1,10 +1,9 @@ package cats package free +import cats.syntax.either._ import scala.annotation.tailrec -import cats.data.Xor - /** * FreeT is a monad transformer for Free monads over a Functor S * @@ -16,7 +15,6 @@ import cats.data.Xor * originally written by Brian McKenna. */ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { - import FreeT._ final def map[B](f: A => B)(implicit M: Applicative[M]): FreeT[S, M, B] = @@ -52,18 +50,18 @@ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { * at each step and accumulating into the monad `M`. */ def foldMap(f: S ~> M)(implicit MR: Monad[M], RT: RecursiveTailRecM[M]): M[A] = { - def go(ft: FreeT[S, M, A]): M[FreeT[S, M, A] Xor A] = + def go(ft: FreeT[S, M, A]): M[Either[FreeT[S, M, A], A]] = ft match { case Suspend(ma) => MR.flatMap(ma) { - case Xor.Left(a) => MR.pure(Xor.Right(a)) - case Xor.Right(sa) => MR.map(f(sa))(Xor.right) + case Left(a) => MR.pure(Right(a)) + case Right(sa) => MR.map(f(sa))(Right(_)) } case g @ FlatMapped(_, _) => g.a match { case Suspend(mx) => MR.flatMap(mx) { - case Xor.Left(x) => MR.pure(Xor.left(g.f(x))) - case Xor.Right(sx) => MR.map(f(sx))(g.f andThen Xor.left) + case Left(x) => MR.pure(Left(g.f(x))) + case Right(sx) => MR.map(f(sx))(x => Left(g.f(x))) } - case g0 @ FlatMapped(_, _) => MR.pure(Xor.left(g0.a.flatMap(g0.f(_).flatMap(g.f)))) + case g0 @ FlatMapped(_, _) => MR.pure(Left(g0.a.flatMap(g0.f(_).flatMap(g.f)))) } } @@ -71,16 +69,16 @@ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { } /** Evaluates a single layer of the free monad */ - def resume(implicit S: Functor[S], MR: Monad[M], RT: RecursiveTailRecM[M]): M[A Xor S[FreeT[S, M, A]]] = { - def go(ft: FreeT[S, M, A]): M[FreeT[S, M, A] Xor (A Xor S[FreeT[S, M, A]])] = + def resume(implicit S: Functor[S], MR: Monad[M], RT: RecursiveTailRecM[M]): M[Either[A, S[FreeT[S, M, A]]]] = { + def go(ft: FreeT[S, M, A]): M[Either[FreeT[S, M, A], Either[A, S[FreeT[S, M, A]]]]] = ft match { - case Suspend(f) => MR.map(f)(as => Xor.right(as.map(S.map(_)(pure(_))))) + case Suspend(f) => MR.map(f)(as => Right(as.map(S.map(_)(pure(_))))) case g1 @ FlatMapped(_, _) => g1.a match { case Suspend(m1) => MR.map(m1) { - case Xor.Left(a) => Xor.left(g1.f(a)) - case Xor.Right(fc) => Xor.right(Xor.right(S.map(fc)(g1.f(_)))) + case Left(a) => Left(g1.f(a)) + case Right(fc) => Right(Right(S.map(fc)(g1.f(_)))) } - case g2 @ FlatMapped(_, _) => MR.pure(Xor.left(g2.a.flatMap(g2.f(_).flatMap(g1.f)))) + case g2 @ FlatMapped(_, _) => MR.pure(Left(g2.a.flatMap(g2.f(_).flatMap(g1.f)))) } } @@ -91,10 +89,10 @@ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { * Runs to completion, using a function that maps the resumption from `S` to a monad `M`. */ def runM(interp: S[FreeT[S, M, A]] => M[FreeT[S, M, A]])(implicit S: Functor[S], MR: Monad[M], RT: RecursiveTailRecM[M]): M[A] = { - def runM2(ft: FreeT[S, M, A]): M[FreeT[S, M, A] Xor A] = + def runM2(ft: FreeT[S, M, A]): M[Either[FreeT[S, M, A], A]] = MR.flatMap(ft.resume) { - case Xor.Left(a) => MR.pure(Xor.right(a)) - case Xor.Right(fc) => MR.map(interp(fc))(Xor.left) + case Left(a) => MR.pure(Right(a)) + case Right(fc) => MR.map(interp(fc))(Left(_)) } RT.sameType(MR).tailRecM(this)(runM2) } @@ -108,13 +106,13 @@ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { private[cats] final def toM(implicit M: Applicative[M]): M[FreeT[S, M, A]] = this match { case Suspend(m) => M.map(m) { - case Xor.Left(a) => pure(a) - case Xor.Right(s) => liftF(s) + case Left(a) => pure(a) + case Right(s) => liftF(s) } case g1 @ FlatMapped(_, _) => g1.a match { case Suspend(m) => M.map(m) { - case Xor.Left(a) => g1.f(a) - case Xor.Right(s) => liftF[S, M, g1.A](s).flatMap(g1.f) + case Left(a) => g1.f(a) + case Right(s) => liftF[S, M, g1.A](s).flatMap(g1.f) } case g0 @ FlatMapped(_, _) => g0.a.flatMap(g0.f(_).flatMap(g1.f)).toM } @@ -135,7 +133,7 @@ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { object FreeT extends FreeTInstances { /** Suspend the computation with the given suspension. */ - private[free] case class Suspend[S[_], M[_], A](a: M[A Xor S[A]]) extends FreeT[S, M, A] + private[free] case class Suspend[S[_], M[_], A](a: M[Either[A, S[A]]]) extends FreeT[S, M, A] /** Call a subroutine and continue with the given function. */ private[free] case class FlatMapped[S[_], M[_], A0, B](a0: FreeT[S, M, A0], f0: A0 => FreeT[S, M, B]) extends FreeT[S, M, B] { @@ -145,22 +143,22 @@ object FreeT extends FreeTInstances { } /** Return the given value in the free monad. */ - def pure[S[_], M[_], A](value: A)(implicit M: Applicative[M]): FreeT[S, M, A] = Suspend(M.pure(Xor.left(value))) + def pure[S[_], M[_], A](value: A)(implicit M: Applicative[M]): FreeT[S, M, A] = Suspend(M.pure(Left(value))) - def suspend[S[_], M[_], A](a: M[A Xor S[FreeT[S, M, A]]])(implicit M: Applicative[M]): FreeT[S, M, A] = + def suspend[S[_], M[_], A](a: M[Either[A, S[FreeT[S, M, A]]]])(implicit M: Applicative[M]): FreeT[S, M, A] = liftT(a).flatMap({ - case Xor.Left(a) => pure(a) - case Xor.Right(s) => roll(s) + case Left(a) => pure(a) + case Right(s) => roll(s) }) - def tailRecM[S[_], M[_]: Applicative, A, B](a: A)(f: A => FreeT[S, M, A Xor B]): FreeT[S, M, B] = + def tailRecM[S[_], M[_]: Applicative, A, B](a: A)(f: A => FreeT[S, M, Either[A, B]]): FreeT[S, M, B] = f(a).flatMap { - case Xor.Left(a0) => tailRecM(a0)(f) - case Xor.Right(b) => pure[S, M, B](b) + case Left(a0) => tailRecM(a0)(f) + case Right(b) => pure[S, M, B](b) } def liftT[S[_], M[_], A](value: M[A])(implicit M: Functor[M]): FreeT[S, M, A] = - Suspend(M.map(value)(Xor.left)) + Suspend(M.map(value)(Left(_))) /** A version of `liftT` that infers the nested type constructor. */ def liftTU[S[_], MA](value: MA)(implicit M: Unapply[Functor, MA]): FreeT[S, M.M, M.A] = @@ -168,7 +166,7 @@ object FreeT extends FreeTInstances { /** Suspends a value within a functor in a single step. Monadic unit for a higher-order monad. */ def liftF[S[_], M[_], A](value: S[A])(implicit M: Applicative[M]): FreeT[S, M, A] = - Suspend(M.pure(Xor.right(value))) + Suspend(M.pure(Right(value))) def roll[S[_], M[_], A](value: S[FreeT[S, M, A]])(implicit M: Applicative[M]): FreeT[S, M, A] = liftF[S, M, FreeT[S, M, A]](value).flatMap(identity) @@ -241,7 +239,7 @@ private[free] sealed trait FreeTFlatMap[S[_], M[_]] extends FlatMap[FreeT[S, M, override final def map[A, B](fa: FreeT[S, M, A])(f: A => B): FreeT[S, M, B] = fa.map(f) def flatMap[A, B](fa: FreeT[S, M, A])(f: A => FreeT[S, M, B]): FreeT[S, M, B] = fa.flatMap(f) - override final def tailRecM[A, B](a: A)(f: A => FreeT[S, M, A Xor B]): FreeT[S, M, B] = + override final def tailRecM[A, B](a: A)(f: A => FreeT[S, M, Either[A, B]]): FreeT[S, M, B] = FreeT.tailRecM(a)(f) } diff --git a/free/src/test/scala/cats/free/FreeTTests.scala b/free/src/test/scala/cats/free/FreeTTests.scala index 721f93a485..d88a101503 100644 --- a/free/src/test/scala/cats/free/FreeTTests.scala +++ b/free/src/test/scala/cats/free/FreeTTests.scala @@ -39,7 +39,7 @@ class FreeTTests extends CatsSuite { } { - implicit val eqXortTFA: Eq[XorT[FreeTOption, Unit, Int]] = XorT.catsDataEqForXorT[FreeTOption, Unit, Int] + implicit val eqEithertTFA: Eq[EitherT[FreeTOption, Unit, Int]] = EitherT.catsDataEqForEitherT[FreeTOption, Unit, Int] checkAll("FreeT[Option, Option, Int]", MonadErrorTests[FreeTOption, Unit].monadError[Int, Int, Int]) checkAll("MonadError[FreeT[Option, Option, ?], Unit]", SerializableTests.serializable(MonadError[FreeTOption, Unit])) } @@ -55,9 +55,9 @@ class FreeTTests extends CatsSuite { val result = Monad[FreeTOption].tailRecM(0)((i: Int) => if (i < 50000) - Applicative[FreeTOption].pure(Xor.left[Int, Unit](i + 1)) + Applicative[FreeTOption].pure(Either.left[Int, Unit](i + 1)) else - Applicative[FreeTOption].pure(Xor.right[Int, Unit](()))) + Applicative[FreeTOption].pure(Either.right[Int, Unit](()))) Eq[FreeTOption[Unit]].eqv(expected, result) should ===(true) } @@ -138,8 +138,8 @@ class FreeTTests extends CatsSuite { } private[free] def liftTUCompilationTests() = { - val a: String Xor Int = Xor.right(42) - val b: FreeT[Option, String Xor ?, Int] = FreeT.liftTU(a) + val a: Either[String, Int]= Right(42) + val b: FreeT[Option, Either[String, ?], Int] = FreeT.liftTU(a) } }