diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index dd81387ede..4f36625205 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 import scala.util.{ Failure, Success, Try } import scala.util.control.NonFatal @@ -37,21 +37,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/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/Eval.scala b/core/src/main/scala/cats/Eval.scala index acd745b399..36348160fe 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,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[A Xor 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) - }) + 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]] = diff --git a/core/src/main/scala/cats/FlatMap.scala b/core/src/main/scala/cats/FlatMap.scala index fdc36e016f..a7767ee808 100644 --- a/core/src/main/scala/cats/FlatMap.scala +++ b/core/src/main/scala/cats/FlatMap.scala @@ -1,6 +1,5 @@ package cats -import cats.data.Xor import simulacrum.typeclass /** @@ -93,7 +92,7 @@ import simulacrum.typeclass flatMap(fa)(if (_) ifTrue else ifFalse) /** - * Keeps calling `f` until a `[[cats.data.Xor.Right 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]]. @@ -106,5 +105,5 @@ import simulacrum.typeclass * using recursive flatMap. Such an implementation will only be stack safe if * the Monad is trampolined. */ - 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/Monad.scala b/core/src/main/scala/cats/Monad.scala index ccd5f41804..b8388fa7b5 100644 --- a/core/src/main/scala/cats/Monad.scala +++ b/core/src/main/scala/cats/Monad.scala @@ -1,6 +1,5 @@ package cats -import cats.data.Xor import simulacrum.typeclass /** @@ -22,9 +21,9 @@ import simulacrum.typeclass * to write this method (all cats types have a stack safe version * of this). When this method is safe you can find an `implicit r: RecursiveTailRecM`. */ - protected def defaultTailRecM[A, B](a: A)(fn: A => F[A Xor B]): F[B] = + protected def defaultTailRecM[A, B](a: A)(fn: A => F[Either[A, B]]): F[B] = flatMap(fn(a)) { - case Xor.Right(b) => pure(b) - case Xor.Left(nextA) => defaultTailRecM(nextA)(fn) + case Right(b) => pure(b) + case Left(nextA) => defaultTailRecM(nextA)(fn) } } 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 31bdc85e87..7c1b4ba008 100644 --- a/core/src/main/scala/cats/arrow/Choice.scala +++ b/core/src/main/scala/cats/arrow/Choice.scala @@ -1,12 +1,9 @@ package cats package arrow -import cats.data.Xor - import simulacrum.typeclass @typeclass trait Choice[F[_, _]] extends Category[F] { - /** * Given two `F`s (`f` and `g`) with a common target type, create a new `F` * with the same target type, but with a source type of either `f`'s source @@ -14,20 +11,19 @@ import simulacrum.typeclass * * 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 @@ -35,16 +31,15 @@ import simulacrum.typeclass * * 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) } 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/Cokleisli.scala b/core/src/main/scala/cats/data/Cokleisli.scala index 32e00990d1..752a200c74 100644 --- a/core/src/main/scala/cats/data/Cokleisli.scala +++ b/core/src/main/scala/cats/data/Cokleisli.scala @@ -58,12 +58,12 @@ private[data] sealed abstract class CokleisliInstances extends CokleisliInstance override def map[B, C](fa: Cokleisli[F, A, B])(f: B => C): Cokleisli[F, A, C] = fa.map(f) - def tailRecM[B, C](b: B)(fn: B => Cokleisli[F, A, B Xor C]): Cokleisli[F, A, C] = + def tailRecM[B, C](b: B)(fn: B => Cokleisli[F, A, Either[B, C]]): Cokleisli[F, A, C] = Cokleisli({ (fa: F[A]) => @tailrec - def loop(c: Cokleisli[F, A, B Xor C]): C = c.run(fa) match { - case Xor.Right(c) => c - case Xor.Left(bb) => loop(fn(bb)) + def loop(c: Cokleisli[F, A, Either[B, C]]): C = c.run(fa) match { + case Right(c) => c + case Left(bb) => loop(fn(bb)) } loop(fn(b)) }) 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..66915a7404 --- /dev/null +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -0,0 +1,452 @@ +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(default: => B)(implicit F: Functor[F]): F[B] = F.map(value)(_.getOrElse(default)) + + 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(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) + }) + } + + 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(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(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]) + + def collectRight(implicit F: MonadCombine[F]): 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))) + + 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[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.rightCast) + case Right(b) => f(b).value + }) + + 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[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) + + 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(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. + * + * 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 eithert: EitherT[Option, Error, Int] = EitherT(Some(Either.left("error 3"))) + * scala> eithert.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)(either => f(either.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](either: Either[E, A])(implicit F: Applicative[F]): EitherT[F, E, A] = + EitherT(F.pure(either)) + } +} + +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 catsDataMonadErrorForEitherT[F[_], L](implicit F0: Monad[F]): MonadError[EitherT[F, L, ?], L] = + new EitherTMonadError[F, L] { implicit val F = F0 } + + implicit def catsDataRecursiveTailRecMForEitherT[F[_]: RecursiveTailRecM, L]: RecursiveTailRecM[EitherT[F, L, ?]] = + RecursiveTailRecM.create[EitherT[F, L, ?]] + + 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 EitherTInstances3 { + 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 + 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 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 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/IdT.scala b/core/src/main/scala/cats/data/IdT.scala index 26cf3f3299..0c619079c0 100644 --- a/core/src/main/scala/cats/data/IdT.scala +++ b/core/src/main/scala/cats/data/IdT.scala @@ -51,7 +51,7 @@ private[data] sealed trait IdTMonad[F[_]] extends Monad[IdT[F, ?]] { def flatMap[A, B](fa: IdT[F, A])(f: A => IdT[F, B]): IdT[F, B] = fa.flatMap(f) - def tailRecM[A, B](a: A)(f: A => IdT[F, A Xor B]): IdT[F, B] = + def tailRecM[A, B](a: A)(f: A => IdT[F, Either[A, B]]): IdT[F, B] = IdT(F0.tailRecM(a)(f(_).value)) } diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index 0ecf85cb97..88801735e2 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -11,14 +11,14 @@ import scala.annotation.tailrec * - `[[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 [[scala.util.Either]] form can be accessed via the [[unwrap]] method. */ sealed abstract class Ior[+A, +B] extends Product with Serializable { @@ -36,10 +36,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)) @@ -144,14 +144,14 @@ private[data] sealed abstract class IorInstances extends IorInstances0 { implicit def catsDataMonadForIor[A: Semigroup]: Monad[A Ior ?] with RecursiveTailRecM[A Ior ?] = new Monad[A Ior ?] with RecursiveTailRecM[A Ior ?] { def pure[B](b: B): A Ior B = Ior.right(b) def flatMap[B, C](fa: A Ior B)(f: B => A Ior C): A Ior C = fa.flatMap(f) - def tailRecM[B, C](b: B)(fn: B => Ior[A, Xor[B, C]]): A Ior C = { + def tailRecM[B, C](b: B)(fn: B => Ior[A, Either[B, C]]): A Ior C = { @tailrec - def loop(v: Ior[A, Xor[B, C]]): A Ior C = v match { + def loop(v: Ior[A, Either[B, C]]): A Ior C = v match { case Ior.Left(a) => Ior.left(a) - case Ior.Right(Xor.Right(c)) => Ior.right(c) - case Ior.Both(a, Xor.Right(c)) => Ior.both(a, c) - case Ior.Right(Xor.Left(b)) => loop(fn(b)) - case Ior.Both(a, Xor.Left(b)) => + case Ior.Right(Right(c)) => Ior.right(c) + case Ior.Both(a, Right(c)) => Ior.both(a, c) + case Ior.Right(Left(b)) => loop(fn(b)) + case Ior.Both(a, Left(b)) => fn(b) match { case Ior.Left(aa) => Ior.left(Semigroup[A].combine(a, aa)) case Ior.Both(aa, x) => loop(Ior.both(Semigroup[A].combine(a, aa), x)) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 2169802f72..f1fbcb07c1 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] = @@ -145,9 +145,10 @@ private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] = fa.map(f) - def tailRecM[B, C](b: B)(f: B => Kleisli[F, A, B Xor C]): Kleisli[F, A, C] = + def tailRecM[B, C](b: B)(f: B => Kleisli[F, A, Either[B, C]]): Kleisli[F, A, C] = Kleisli[F, A, C]({ a => FlatMap[F].tailRecM(b) { f(_).run(a) } }) } + implicit def catsDataRecursiveTailRecMForKleisli[F[_]: RecursiveTailRecM, A]: RecursiveTailRecM[Kleisli[F, A, ?]] = RecursiveTailRecM.create[Kleisli[F, A, ?]] @@ -200,7 +201,7 @@ private[data] sealed abstract class KleisliInstances4 { def local[B](f: A => A)(fa: Kleisli[F, A, B]): Kleisli[F, A, B] = Kleisli(f.andThen(fa.run)) - def tailRecM[B, C](b: B)(f: B => Kleisli[F, A, B Xor C]): Kleisli[F, A, C] = + def tailRecM[B, C](b: B)(f: B => Kleisli[F, A, Either[B, C]]): Kleisli[F, A, C] = Kleisli[F, A, C]({ a => FlatMap[F].tailRecM(b) { f(_).run(a) } }) } } diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index ccb571422a..9aaffa7bde 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -196,16 +196,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 6da76beaa8..fb6824d664 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -196,16 +196,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/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index a0a14a402f..ee50730ebb 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -137,14 +137,14 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { OneAnd(fst.head, monad.combineK(fst.tail, end)) } - def tailRecM[A, B](a: A)(fn: A => OneAnd[F, A Xor B]): OneAnd[F, B] = { - def stepF(a: A): F[A Xor B] = { + def tailRecM[A, B](a: A)(fn: A => OneAnd[F, Either[A, B]]): OneAnd[F, B] = { + def stepF(a: A): F[Either[A, B]] = { val oneAnd = fn(a) monad.combineK(monad.pure(oneAnd.head), oneAnd.tail) } - def toFB(in: A Xor B): F[B] = in match { - case Xor.Right(b) => monad.pure(b) - case Xor.Left(a) => monad.tailRecM(a)(stepF) + def toFB(in: Either[A, B]): F[B] = in match { + case Right(b) => monad.pure(b) + case Left(a) => monad.tailRecM(a)(stepF) } // This could probably be in SemigroupK to perform well @@ -158,10 +158,10 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { @tailrec def go(in: A, rest: List[F[B]]): OneAnd[F, B] = fn(in) match { - case OneAnd(Xor.Right(b), tail) => + case OneAnd(Right(b), tail) => val fbs = monad.flatMap(tail)(toFB) OneAnd(b, combineAll(fbs :: rest)) - case OneAnd(Xor.Left(a), tail) => + case OneAnd(Left(a), tail) => val fbs = monad.flatMap(tail)(toFB) go(a, fbs :: rest) } diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index b754222fec..65c42c4634 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) @@ -251,9 +252,9 @@ private[data] trait OptionTMonad[F[_]] extends Monad[OptionT[F, ?]] { override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = fa.map(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 11d8732c7d..75b0c10401 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 @@ -217,7 +219,7 @@ private[data] sealed trait StateTMonad[F[_], S] extends Monad[StateT[F, S, ?]] { override def map[A, B](fa: StateT[F, S, A])(f: A => B): StateT[F, S, B] = fa.map(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 cce53e1a74..da67b00d4d 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/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index ee9e956407..f0c683b595 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -248,17 +248,17 @@ private[data] sealed trait WriterTFlatMap1[F[_], L] extends WriterTApply[F, L] w def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = fa.flatMap(f) - def tailRecM[A, B](a: A)(fn: A => WriterT[F, L, A Xor B]): WriterT[F, L, B] = { + def tailRecM[A, B](a: A)(fn: A => WriterT[F, L, Either[A, B]]): WriterT[F, L, B] = { - def step(la: (L, A)): F[(L, A) Xor (L, B)] = { - val flv: F[(L, A Xor B)] = fn(la._2).run + def step(la: (L, A)): F[Either[(L, A), (L, B)]] = { + val flv: F[(L, Either[A, B])] = fn(la._2).run F0.map(flv) { - case (l, Xor.Left(a)) => + case (l, Left(a)) => val combineL = L0.combine(la._1, l) - Xor.left((combineL, a)) - case (l, Xor.Right(b)) => + Left((combineL, a)) + case (l, Right(b)) => val combineL = L0.combine(la._1, l) - Xor.right((combineL, b)) + Right((combineL, b)) } } @@ -273,24 +273,24 @@ private[data] sealed trait WriterTFlatMap2[F[_], L] extends WriterTApply[F, L] w def flatMap[A, B](fa: WriterT[F, L, A])(f: A => WriterT[F, L, B]): WriterT[F, L, B] = fa.flatMap(f) - def tailRecM[A, B](a: A)(fn: A => WriterT[F, L, A Xor B]): WriterT[F, L, B] = { + def tailRecM[A, B](a: A)(fn: A => WriterT[F, L, Either[A, B]]): WriterT[F, L, B] = { - def step(la: (L, A)): F[(L, A) Xor (L, B)] = { - val flv: F[(L, A Xor B)] = fn(la._2).run + def step(la: (L, A)): F[Either[(L, A), (L, B)]] = { + val flv: F[(L, Either[A, B])] = fn(la._2).run F0.map(flv) { - case (l, Xor.Left(a)) => + case (l, Left(a)) => val combineL = L0.combine(la._1, l) - Xor.left((combineL, a)) - case (l, Xor.Right(b)) => + Left((combineL, a)) + case (l, Right(b)) => val combineL = L0.combine(la._1, l) - Xor.right((combineL, b)) + Right((combineL, b)) } } val init = fn(a).run val res: F[(L, B)] = F0.flatMap(init) { - case (l, Xor.Right(b)) => F0.pure((l, b)) - case (l, Xor.Left(a)) => F0.tailRecM((l, a))(step) + case (l, Right(b)) => F0.pure((l, b)) + case (l, Left(a)) => F0.tailRecM((l, a))(step) } WriterT(res) } diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 8fc366d9dd..077d58be73 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 ed37c2afd3..2ea3ab724b 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]] = @@ -366,11 +368,11 @@ private[data] trait XorTMonad[F[_], L] extends Monad[XorT[F, L, ?]] with XorTFun implicit val F: Monad[F] def pure[A](a: A): XorT[F, L, A] = XorT(F.pure(Xor.right(a))) def flatMap[A, B](fa: XorT[F, L, A])(f: A => XorT[F, L, B]): XorT[F, L, B] = fa flatMap f - def tailRecM[A, B](a: A)(f: A => XorT[F, L, A Xor 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)) + 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) => Right(Xor.Left(l)) + case Xor.Right(Left(a1)) => Left(a1) + case Xor.Right(Right(b)) => Right(Xor.Right(b)) })) } @@ -386,7 +388,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] = 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 82d52ed90f..d5b3cd628f 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -1,8 +1,9 @@ package cats package instances +import cats.syntax.EitherUtil +import cats.syntax.either._ import scala.annotation.tailrec -import cats.data.Xor trait EitherInstances extends EitherInstances1 { implicit val catsStdBitraverseForEither: Bitraverse[Either] = @@ -26,30 +27,35 @@ trait EitherInstances extends EitherInstances1 { } } - implicit def catsStdInstancesForEither[A]: Monad[Either[A, ?]] with Traverse[Either[A, ?]] with RecursiveTailRecM[Either[A, ?]] = - new Monad[Either[A, ?]] with Traverse[Either[A, ?]] with RecursiveTailRecM[Either[A, ?]] { + // 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) 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]] = 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.rightCast(l)) case Right(b) => fc.map(_.right.map(f(b, _))) } @@ -64,7 +70,16 @@ 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) } + // 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( @@ -80,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/instances/function.scala b/core/src/main/scala/cats/instances/function.scala index 530623857c..bf01a45fe9 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 import annotation.tailrec @@ -20,12 +19,12 @@ private[instances] sealed trait Function0Instances { def flatMap[A, B](fa: () => A)(f: A => () => B): () => B = () => f(fa())() - def tailRecM[A, B](a: A)(fn: A => () => Xor[A, B]): () => B = + def tailRecM[A, B](a: A)(fn: A => () => Either[A, B]): () => B = () => { @tailrec def loop(thisA: A): B = fn(thisA)() match { - case Xor.Right(b) => b - case Xor.Left(nextA) => loop(nextA) + case Right(b) => b + case Left(nextA) => loop(nextA) } loop(a) } @@ -58,12 +57,12 @@ private[instances] sealed trait Function1Instances extends Function1Instances0 { override def map[R1, R2](fa: T1 => R1)(f: R1 => R2): T1 => R2 = f.compose(fa) - def tailRecM[A, B](a: A)(fn: A => T1 => Xor[A, B]): T1 => B = + def tailRecM[A, B](a: A)(fn: A => T1 => Either[A, B]): T1 => B = (t: T1) => { @tailrec def step(thisA: A): B = fn(thisA)(t) match { - case Xor.Right(b) => b - case Xor.Left(nextA) => step(nextA) + case Right(b) => b + case Left(nextA) => step(nextA) } step(a) } @@ -71,10 +70,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 750f7e5bb1..216a56c426 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 7b70549a37..cfbfc7cf0d 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 Monad[List] with CoflatMap[List] with RecursiveTailRecM[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/map.scala b/core/src/main/scala/cats/instances/map.scala index 678a94695e..166f33b9c3 100644 --- a/core/src/main/scala/cats/instances/map.scala +++ b/core/src/main/scala/cats/instances/map.scala @@ -1,7 +1,6 @@ package cats package instances -import cats.data.Xor import scala.annotation.tailrec trait MapInstances extends cats.kernel.instances.MapInstances { @@ -57,17 +56,17 @@ trait MapInstances extends cats.kernel.instances.MapInstances { def foldRight[A, B](fa: Map[K, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = Foldable.iterateRight(fa.values.iterator, lb)(f) - def tailRecM[A, B](a: A)(f: A => Map[K, A Xor B]): Map[K, B] = { + def tailRecM[A, B](a: A)(f: A => Map[K, Either[A, B]]): Map[K, B] = { val bldr = Map.newBuilder[K, B] - @tailrec def descend(k: K, xor: Xor[A, B]): Unit = - xor match { - case Xor.Left(a) => + @tailrec def descend(k: K, either: Either[A, B]): Unit = + either match { + case Left(a) => f(a).get(k) match { case Some(x) => descend(k, x) case None => () } - case Xor.Right(b) => + case Right(b) => bldr += ((k, b)) () } diff --git a/core/src/main/scala/cats/instances/option.scala b/core/src/main/scala/cats/instances/option.scala index c2caaf30d7..b9ca152996 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/stream.scala b/core/src/main/scala/cats/instances/stream.scala index 44933076f2..131170c9bc 100644 --- a/core/src/main/scala/cats/instances/stream.scala +++ b/core/src/main/scala/cats/instances/stream.scala @@ -1,7 +1,6 @@ package cats package instances -import cats.data.Xor import cats.syntax.show._ import scala.annotation.tailrec @@ -55,38 +54,38 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances { }.value } - def tailRecM[A, B](a: A)(fn: A => Stream[A Xor B]): Stream[B] = { + def tailRecM[A, B](a: A)(fn: A => Stream[Either[A, B]]): Stream[B] = { val it: Iterator[B] = new Iterator[B] { - var stack: Stream[A Xor B] = fn(a) - var state: Xor[Unit, Option[B]] = Xor.left(()) + var stack: Stream[Either[A, B]] = fn(a) + var state: Either[Unit, Option[B]] = Left(()) @tailrec def advance(): Unit = stack match { - case Xor.Right(b) #:: tail => + case Right(b) #:: tail => stack = tail - state = Xor.Right(Some(b)) - case Xor.Left(a) #:: tail => + state = Right(Some(b)) + case Left(a) #:: tail => stack = fn(a) #::: tail advance case empty => - state = Xor.Right(None) + state = Right(None) } @tailrec def hasNext: Boolean = state match { - case Xor.Left(()) => + case Left(()) => advance() hasNext - case Xor.Right(o) => + case Right(o) => o.isDefined } @tailrec def next(): B = state match { - case Xor.Left(()) => + case Left(()) => advance() next - case Xor.Right(o) => + case Right(o) => val b = o.get advance() b diff --git a/core/src/main/scala/cats/instances/try.scala b/core/src/main/scala/cats/instances/try.scala index 7ae2520e7b..e96d6bb511 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/instances/vector.scala b/core/src/main/scala/cats/instances/vector.scala index 64a48f435e..8126233b32 100644 --- a/core/src/main/scala/cats/instances/vector.scala +++ b/core/src/main/scala/cats/instances/vector.scala @@ -1,7 +1,6 @@ package cats package instances -import cats.data.Xor import cats.syntax.show._ import scala.annotation.tailrec import scala.collection.+: @@ -49,7 +48,7 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { G.map2Eval(f(a), lgvb)((ob, v) => ob.fold(v)(_ +: v)) }.value - def tailRecM[A, B](a: A)(fn: A => Vector[A Xor B]): Vector[B] = { + def tailRecM[A, B](a: A)(fn: A => Vector[Either[A, B]]): Vector[B] = { val buf = Vector.newBuilder[B] var state = List(fn(a).iterator) @tailrec @@ -60,10 +59,10 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { loop() case h :: tail => h.next match { - case Xor.Right(b) => + case Right(b) => buf += b loop() - case Xor.Left(a) => + case Left(a) => state = (fn(a).iterator) :: h :: tail loop() } diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index b1d72a0fca..e0708dd787 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..f4809f410a 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -1,13 +1,232 @@ package cats package syntax -import cats.data.Xor +import cats.data.{EitherT, 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) // 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 { + def foreach(f: B => Unit): Unit = eab match { + case Left(_) => () + case Right(b) => f(b) + } + + def getOrElse(default: => B): B = eab match { + case Left(_) => default + case Right(b) => b + } + + def orElse[C](fallback: => Either[C, B]): Either[C, B] = eab match { + case Left(_) => fallback + case r @ Right(_) => EitherUtil.leftCast(r) + } + + 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(pf: PartialFunction[A, Either[A, B]]): Either[A, B] = eab match { + case Left(a) if pf.isDefinedAt(a) => pf(a) + case _ => eab + } + + def valueOr(f: A => B): B = eab match { + case Left(a) => f(a) + case Right(b) => b + } + + def forall(f: B => Boolean): Boolean = eab match { + case Left(_) => true + case Right(b) => f(b) + } + + def exists(f: B => Boolean): Boolean = eab match { + case Left(_) => false + case Right(b) => f(b) + } + + 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 match { + case Left(a) => Ior.left(a) + case Right(b) => Ior.right(b) + } + + def toOption: Option[B] = eab match { + case Left(_) => None + case Right(b) => Some(b) + } + + def toList: List[B] = eab match { + case Left(_) => Nil + case Right(b) => List(b) + } + + 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 match { + case Left(a) => Validated.invalid(a) + case Right(b) => Validated.valid(b) + } + + /** 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[_]](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)) + case Right(b) => Right(fb(b)) + } + + def map[C](f: B => C): Either[A, C] = eab match { + 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.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.leftCast(r) + } + + def flatMap[D](f: B => Either[A, D]): Either[A, D] = eab match { + case l @ Left(_) => EitherUtil.rightCast(l) + case Right(b) => f(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(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 ===(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[_], D](f: B => F[D])(implicit F: Applicative[F]): F[Either[A, D]] = eab match { + case l @ Left(_) => F.pure(EitherUtil.rightCast(l)) + case Right(b) => F.map(f(b))(Right(_)) + } + + 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 match { + case Left(_) => lc + case Right(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(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(B.combine(b1, b2)) + } + } + + 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[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]]. @@ -27,4 +246,88 @@ 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 [[cats.data.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 { // 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) + + /** + * 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 match { + case None => left[A, B](ifNone) + case Some(a) => right(a) + } +} + +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]) + } +} + +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 { + def leftCast[A, B, C](r: Right[A, B]): Either[C, B] = new RightOps(r).leftCast[C] + + def rightCast[A, B, C](l: Left[A, B]): Either[A, C] = new LeftOps(l).rightCast[C] } 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/kleisli.md b/docs/src/main/tut/kleisli.md index 20bf99860f..f446cb8015 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -138,7 +138,7 @@ implicit def kleisliFlatMap[F[_], Z](implicit F: FlatMap[F]): FlatMap[Kleisli[F, def map[A, B](fa: Kleisli[F, Z, A])(f: A => B): Kleisli[F, Z, B] = Kleisli(z => fa.run(z).map(f)) - def tailRecM[A, B](a: A)(f: A => Kleisli[F, Z, A Xor B]) = + def tailRecM[A, B](a: A)(f: A => Kleisli[F, Z, Either[A, B]]) = Kleisli[F, Z, B]({ z => FlatMap[F].tailRecM(a) { f(_).run(z) } }) } ``` diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index 4591279450..54626636d9 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -35,7 +35,6 @@ the identity function `x => x` (i.e. `flatMap(_)(x => x)`). ```tut:silent import cats._ -import cats.data.Xor implicit def optionMonad(implicit app: Applicative[Option]) = new Monad[Option] { @@ -46,11 +45,11 @@ implicit def optionMonad(implicit app: Applicative[Option]) = override def pure[A](a: A): Option[A] = app.pure(a) @annotation.tailrec - def tailRecM[A, B](init: A)(fn: A => Option[A Xor B]): Option[B] = + def tailRecM[A, B](init: A)(fn: A => Option[Either[A, B]]): Option[B] = fn(init) match { case None => None - case Some(Xor.Right(b)) => Some(b) - case Some(Xor.Left(a)) => tailRecM(a)(fn) + case Some(Right(b)) => Some(b) + case Some(Left(a)) => tailRecM(a)(fn) } } ``` @@ -65,7 +64,7 @@ derived from `flatMap` and `pure`. implicit val listMonad = new Monad[List] { def flatMap[A, B](fa: List[A])(f: A => List[B]): List[B] = fa.flatMap(f) def pure[A](a: A): List[A] = List(a) - 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] = defaultTailRecM(a)(f) } ``` @@ -120,7 +119,7 @@ implicit def optionTMonad[F[_]](implicit F : Monad[F]) = { case Some(a) => f(a).value } } - 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] = defaultTailRecM(a)(f) } } diff --git a/docs/src/main/tut/validated.md b/docs/src/main/tut/validated.md index 542d1791f6..d398e60829 100644 --- a/docs/src/main/tut/validated.md +++ b/docs/src/main/tut/validated.md @@ -253,10 +253,10 @@ implicit def validatedMonad[E]: Monad[Validated[E, ?]] = def pure[A](x: A): Validated[E, A] = Valid(x) @annotation.tailrec - def tailRecM[A, B](a: A)(f: A => Validated[E, A Xor B]): Validated[E, B] = + def tailRecM[A, B](a: A)(f: A => Validated[E, Either[A, B]]): Validated[E, B] = f(a) match { - case Valid(Xor.Right(b)) => Valid(b) - case Valid(Xor.Left(a)) => tailRecM(a)(f) + case Valid(Right(b)) => Valid(b) + case Valid(Left(a)) => tailRecM(a)(f) case i@Invalid(_) => i } } @@ -314,19 +314,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/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index 2ea0afd8ee..ad14832991 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -113,10 +113,10 @@ implicit def xorMonad[Err]: Monad[Xor[Err, ?]] = def pure[A](x: A): Xor[Err, A] = Xor.right(x) @annotation.tailrec - def tailRecM[A, B](a: A)(f: A => Xor[Err, A Xor B]): Xor[Err, B] = + def tailRecM[A, B](a: A)(f: A => Xor[Err, Either[A, B]]): Xor[Err, B] = f(a) match { - case Xor.Right(r@Xor.Right(_)) => r - case Xor.Right(Xor.Left(a)) => tailRecM(a)(f) + case Xor.Right(Right(b)) => Xor.right(b) + case Xor.Right(Left(a)) => tailRecM(a)(f) case l@Xor.Left(_) => l } } diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 4f39e42b6a..0ccbca977e 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: Monad[S], r: RecursiveTailRecM[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)))) } } r.sameType(S).tailRecM(this)(step) @@ -127,9 +126,9 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { */ final def foldMap[M[_]](f: FunctionK[S, M])(implicit M: Monad[M], r: RecursiveTailRecM[M]): M[A] = r.sameType(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))) }) /** @@ -214,7 +213,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 2a9c2556a3..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 @@ -53,7 +52,7 @@ class FreeTests extends CatsSuite { test("tailRecM is stack safe") { val n = 50000 val fa = Monad[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)) } @@ -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/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/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/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/FlatMapLaws.scala b/laws/src/main/scala/cats/laws/FlatMapLaws.scala index c29523c07c..5b87c86646 100644 --- a/laws/src/main/scala/cats/laws/FlatMapLaws.scala +++ b/laws/src/main/scala/cats/laws/FlatMapLaws.scala @@ -1,7 +1,7 @@ package cats package laws -import cats.data.{ Kleisli, Xor } +import cats.data.Kleisli import cats.syntax.apply._ import cats.syntax.flatMap._ import cats.syntax.functor._ @@ -29,8 +29,8 @@ trait FlatMapLaws[F[_]] extends ApplyLaws[F] { def tailRecMConsistentFlatMap[A](count: Int, a: A, f: A => F[A]): IsEq[F[A]] = { def bounce(n: Int) = F.tailRecM[(A, Int), A]((a, n)) { 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(_)) } /* * The law is for n >= 1 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/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/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/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/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/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..5db908b543 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, ?]]] @@ -36,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] @@ -55,4 +75,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] should === (x.toList) + } + } + + test("to consistent with toOption") { + forAll { (x: Either[Int, String]) => + x.to[Option] should === (x.toOption) + } + } } 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 92ae4c6e25..4c2dad7efa 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -90,7 +90,7 @@ object ListWrapper { def combineK[A](x: ListWrapper[A], y: ListWrapper[A]): ListWrapper[A] = ListWrapper(M.combineK(x.list, y.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/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/MonadRecInstancesTests.scala b/tests/src/test/scala/cats/tests/MonadRecInstancesTests.scala index 9e8e1a366c..35afb2d594 100644 --- a/tests/src/test/scala/cats/tests/MonadRecInstancesTests.scala +++ b/tests/src/test/scala/cats/tests/MonadRecInstancesTests.scala @@ -3,6 +3,7 @@ package tests import cats.data.{ Cokleisli, + EitherT, IdT, Ior, Kleisli, @@ -19,7 +20,7 @@ import cats.data.{ class MonadRecInstancesTests extends CatsSuite { def tailRecMStackSafety[M[_]: RecursiveTailRecM](implicit M: Monad[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)) } @@ -79,6 +80,10 @@ class MonadRecInstancesTests extends CatsSuite { tailRecMStackSafety[XorT[Option, String, ?]] } + test("tailRecM stack-safety for EitherT") { + tailRecMStackSafety[EitherT[Option, String, ?]] + } + test("tailRecM stack-safety for Ior") { tailRecMStackSafety[Int Ior ?] } diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 39b65dd260..041d72d0e0 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._ @@ -68,32 +68,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, ?]] } { @@ -219,9 +219,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)) } } @@ -256,8 +256,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 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 0970da76c1..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 @@ -20,10 +20,10 @@ class RegressionTests extends CatsSuite { implicit def instance[S]: Monad[State[S, ?]] = new Monad[State[S, ?]] { def pure[A](a: A): State[S, A] = State(s => (a, s)) def flatMap[A, B](sa: State[S, A])(f: A => State[S, B]): State[S, B] = sa.flatMap(f) - def tailRecM[A, B](a: A)(fn: A => State[S, A Xor B]): State[S, B] = + def tailRecM[A, B](a: A)(fn: A => State[S, Either[A, B]]): State[S, B] = flatMap(fn(a)) { - case Xor.Left(a) => tailRecM(a)(fn) - case Xor.Right(b) => pure(b) + case Left(a) => tailRecM(a)(fn) + case Right(b) => pure(b) } } } @@ -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 7ca9b3d55d..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.{NonEmptyList, Validated, ValidatedNel, Xor, XorT} +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._ @@ -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))) } } @@ -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) } } diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index ca610b8e77..1f589afe97 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._ @@ -348,8 +348,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] @@ -365,7 +365,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 edce0887b2..4912d5f83c 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 8b110ed41b..10b60c16a4 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]))