From 3bc775fcfe53609c384c877665be179560c8b1be Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 14 Aug 2016 18:15:47 -0700 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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]