From 3457276881fd748143712e097df68d138feb60df Mon Sep 17 00:00:00 2001 From: Alexandru Nedelcu Date: Mon, 23 Apr 2018 08:33:57 +0300 Subject: [PATCH 1/6] Enhance EitherT and fix #2161: add MonadError.redeem/redeemWith --- build.sbt | 7 + .../cats/internals/FutureShims.scala | 20 + .../cats/internals/FutureShims.scala | 30 + .../main/scala/cats/ApplicativeError.scala | 6 +- core/src/main/scala/cats/MonadError.scala | 70 ++ core/src/main/scala/cats/Parallel.scala | 34 +- core/src/main/scala/cats/data/EitherT.scala | 1056 ++++++++++++++++- core/src/main/scala/cats/data/Ior.scala | 4 +- .../main/scala/cats/instances/either.scala | 52 +- .../main/scala/cats/instances/future.scala | 45 +- .../main/scala/cats/instances/option.scala | 14 + core/src/main/scala/cats/instances/try.scala | 9 +- .../scala/cats/internals/EitherUtil.scala | 35 + core/src/main/scala/cats/syntax/either.scala | 9 +- .../main/scala/cats/laws/MonadErrorLaws.scala | 6 + .../laws/discipline/MonadErrorTests.scala | 4 +- scalastyle-config.xml | 5 - .../scala/cats/tests/RegressionSuite.scala | 15 - 18 files changed, 1275 insertions(+), 146 deletions(-) create mode 100644 core/src/main/scala-2.11-/cats/internals/FutureShims.scala create mode 100644 core/src/main/scala-2.12+/cats/internals/FutureShims.scala create mode 100644 core/src/main/scala/cats/internals/EitherUtil.scala diff --git a/build.sbt b/build.sbt index 7065a20f22..d6ccb5f97e 100644 --- a/build.sbt +++ b/build.sbt @@ -49,6 +49,13 @@ lazy val commonSettings = Seq( scalacOptions in (Compile, doc) := (scalacOptions in (Compile, doc)).value.filter(_ != "-Xfatal-warnings"), ivyConfigurations += CompileTime, unmanagedClasspath in Compile ++= update.value.select(configurationFilter(CompileTime.name)), + unmanagedSourceDirectories in Compile ++= { + val bd = baseDirectory.value + if (CrossVersion.partialVersion(scalaVersion.value) exists (_._2 >= 12)) + CrossType.Pure.sharedSrcDir(bd, "main").toList map (f => file(f.getPath + "-2.12+")) + else + CrossType.Pure.sharedSrcDir(bd, "main").toList map (f => file(f.getPath + "-2.11-")) + }, unmanagedSourceDirectories in Test ++= { val bd = baseDirectory.value if (CrossVersion.partialVersion(scalaVersion.value) exists (_._2 >= 11)) diff --git a/core/src/main/scala-2.11-/cats/internals/FutureShims.scala b/core/src/main/scala-2.11-/cats/internals/FutureShims.scala new file mode 100644 index 0000000000..16969e8c82 --- /dev/null +++ b/core/src/main/scala-2.11-/cats/internals/FutureShims.scala @@ -0,0 +1,20 @@ +package cats.internals + +import scala.concurrent.{ExecutionContext, Future} + +private[cats] object FutureShims { + /** + * Exposes the new `Future#transformWith` from Scala 2.12 in a way + * that's compatible with Scala 2.11. + */ + def redeemWith[A, B](fa: Future[A])(fe: Throwable => Future[B], fs: A => Future[B]) + (implicit ec: ExecutionContext): Future[B] = + attempt(fa).flatMap(_.fold(fe, fs)) + + /** + * Exposes an optimized `attempt` for `Future` whose implementation + * depends on the Scala version. + */ + def attempt[A](fa: Future[A])(implicit ec: ExecutionContext): Future[Either[Throwable, A]] = + fa.map(Right[Throwable, A]).recover { case e => Left(e) } +} diff --git a/core/src/main/scala-2.12+/cats/internals/FutureShims.scala b/core/src/main/scala-2.12+/cats/internals/FutureShims.scala new file mode 100644 index 0000000000..6d2393588b --- /dev/null +++ b/core/src/main/scala-2.12+/cats/internals/FutureShims.scala @@ -0,0 +1,30 @@ +package cats.internals + +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} + +private[cats] object FutureShims { + /** + * Exposes the new `Future#transformWith` from Scala 2.12 in a way + * that's compatible with Scala 2.11. + */ + def redeemWith[A, B](fa: Future[A])(fe: Throwable => Future[B], fs: A => Future[B]) + (implicit ec: ExecutionContext): Future[B] = { + + fa.transformWith { + case Success(a) => fs(a) + case Failure(e) => fe(e) + } + } + + /** + * Exposes an optimized `attempt` for `Future` whose implementation + * depends on the Scala version. + */ + def attempt[A](fa: Future[A])(implicit ec: ExecutionContext): Future[Either[Throwable, A]] = + fa.transformWith(r => Future.successful( + r match { + case Success(a) => Right(a) + case Failure(e) => Left(e) + })) +} diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index b450ee30f9..68508b48c7 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -3,6 +3,7 @@ package cats import cats.data.EitherT import scala.util.{ Failure, Success, Try } import scala.util.control.NonFatal +import cats.internals.EitherUtil.rightBox /** * An applicative that also allows you to raise and or handle an error value. @@ -62,9 +63,8 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { * * All non-fatal errors should be handled by this method. */ - def attempt[A](fa: F[A]): F[Either[E, A]] = handleErrorWith( - map(fa)(Right(_): Either[E, A]) - )(e => pure(Left(e))) + def attempt[A](fa: F[A]): F[Either[E, A]] = + handleErrorWith(map(fa)(rightBox[E, A]))(e => pure(Left[E, A](e))) /** * Similar to [[attempt]], but wraps the result in a [[cats.data.EitherT]] for diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index f861f82803..bc75205b22 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -61,6 +61,76 @@ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { */ def rethrow[A](fa: F[Either[E, A]]): F[A] = flatMap(fa)(_.fold(raiseError, pure)) + + /** + * Returns a new value that transforms the result of the source, + * given the `recover` or `map` functions, which get executed depending + * on whether the result is successful or if it ends in error. + * + * This is an optimization on usage of [[attempt]] and [[map]], + * this equivalence being available: + * + * {{{ + * fa.redeem(fe, fs) <-> fa.attempt.map(_.fold(fe, fs)) + * }}} + * + * Usage of `redeem` subsumes [[handleError]] because: + * + * {{{ + * fa.redeem(fe, id) <-> fa.handleError(fe) + * }}} + * + * Implementations are free to override it in order to optimize + * error recovery. + * + * @see [[redeemWith]], [[attempt]] and [[handleError]] + * + * @param fa is the source whose result is going to get transformed + * @param recover is the function that gets called to recover the source + * in case of error + * @param map is the function that gets to transform the source + * in case of success + */ + def redeem[A, B](fa: F[A])(recover: E => B, map: A => B): F[B] = + redeemWith(fa)(recover.andThen(pure), map.andThen(pure)) + + /** + * Returns a new value that transforms the result of the source, + * given the `recover` or `bind` functions, which get executed depending + * on whether the result is successful or if it ends in error. + * + * This is an optimization on usage of [[attempt]] and [[flatMap]], + * this equivalence being available: + * + * {{{ + * fa.redeemWith(fe, fs) <-> fa.attempt.flatMap(_.fold(fe, fs)) + * }}} + * + * Usage of `redeemWith` subsumes [[handleErrorWith]] because: + * + * {{{ + * fa.redeemWith(fe, F.pure) <-> fa.handleErrorWith(fe) + * }}} + * + * Usage of `redeemWith` also subsumes [[flatMap]] because: + * + * {{{ + * fa.redeemWith(F.raiseError, fs) <-> fa.flatMap(fs) + * }}} + * + * Implementations are free to override it in order to optimize + * error recovery. + * + * @see [[redeem]], [[attempt]] and [[handleErrorWith]] + * + * @param fa is the source whose result is going to get transformed + * @param recover is the function that gets called to recover the source + * in case of error + * @param bind is the function that gets to transform the source + * in case of success + */ + def redeemWith[A, B](fa: F[A])(recover: E => F[B], bind: A => F[B]): F[B] = + flatMap(attempt(fa))(_.fold(recover, bind)) } object MonadError { diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 3d48d4fe03..d31537bf81 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -78,30 +78,32 @@ trait Parallel[M[_], F[_]] extends NonEmptyParallel[M, F] { */ def applicativeError[E](implicit E: MonadError[M, E]): ApplicativeError[F, E] = new ApplicativeError[F, E] { - def raiseError[A](e: E): F[A] = + override def raiseError[A](e: E): F[A] = parallel(MonadError[M, E].raiseError(e)) - def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { + override def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { val ma = MonadError[M, E].handleErrorWith(sequential(fa))(f andThen sequential.apply) parallel(ma) } - def pure[A](x: A): F[A] = applicative.pure(x) - - def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = applicative.ap(ff)(fa) - - override def map[A, B](fa: F[A])(f: (A) => B): F[B] = applicative.map(fa)(f) - - override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = applicative.product(fa, fb) - - override def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = applicative.map2(fa, fb)(f) - + override def pure[A](x: A): F[A] = + applicative.pure(x) + override def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = + applicative.ap(ff)(fa) + override def attempt[A](fa: F[A]): F[Either[E, A]] = + parallel(MonadError[M, E].attempt(sequential(fa))) + override def map[A, B](fa: F[A])(f: (A) => B): F[B] = + applicative.map(fa)(f) + override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = + applicative.product(fa, fb) + override def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = + applicative.map2(fa, fb)(f) override def map2Eval[A, B, Z](fa: F[A], fb: Eval[F[B]])(f: (A, B) => Z): Eval[F[Z]] = applicative.map2Eval(fa, fb)(f) - - override def unlessA[A](cond: Boolean)(f: => F[A]): F[Unit] = applicative.unlessA(cond)(f) - - override def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = applicative.whenA(cond)(f) + override def unlessA[A](cond: Boolean)(f: => F[A]): F[Unit] = + applicative.unlessA(cond)(f) + override def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = + applicative.whenA(cond)(f) } } diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 516034d7d0..470607719f 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -1,123 +1,1058 @@ package cats package data -import cats.Bifunctor import cats.instances.either._ import cats.syntax.either._ +import cats.internals.EitherUtil.{eitherCast, rightBox, leftBox} /** - * Transformer for `Either`, allowing the effect of an arbitrary type constructor `F` to be combined with the - * fail-fast effect of `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`. + * `EitherT[F, A, B]` wraps a value of type `F[Either[A, B]]`. + * + * For example `EitherT` can be combined with types such as [[Eval]], + * `scala.concurrent.Future` or `cats.effect.IO` for principled error + * handling: + * + * {{{ + * import cats.Eval + * import cats.Eval.always + * import cats.data.EitherT + * import java.lang.Long.{parseNum => javaParseNum} + * + * def parseNum(s: String, radix: Int = 10): EitherT[Eval, NumberFormatException, Long] = + * EitherT(always { + * try + * Right(javaParseNum(s, radix)) + * catch { case e: NumberFormatException => + * Left(e) + * } + * }) + * }}} + * + * Note that in this function the error type is part of the signature and + * is more specific than the customary `Throwable` or `Exception`. You can + * always "upcast" its error type to `Throwable` later using [[upcastL]]: + * + * {{{ + * val num: EitherT[Eval, Throwable, Long] = + * parseNum("10210", 10).upcastL + * }}} + * + * The power of `EitherT` is that it combines `F[_]` with the `Either` + * data type, with the result still being a `MonadError`, so you can + * comfortably use operations such as [[map]], [[flatMap]], [[attempt]] + * and others: + * + * {{{ + * def parseEvenNum(s: String, radix: Int = 10): EitherT[Eval, NumberFormatException, Long] = + * parseNum(s, radix).flatMap { i => + * if (i % 2 == 0) + * EitherT.rightT(i) + * else + * EitherT.leftT(new NumberFormatException(s"Not an even number: $$i")) + * } + * }}} + * + * Tip: An `F[A]` can be lifted in to `EitherT[F, E, A]` via [[EitherT.right]] + * and lifted in to a `EitherT[F, A, B]` via [[EitherT.left]]. + * + * {{{ + * def rand(seed: Long): Eval[Int] = + * Eval.always { + * val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL + * (newSeed >>> 16).toInt + * } + * + * // Lifting values + * def randPredicate(seed: Long)(p: Int => Boolean): EitherT[Eval, String, Int] = + * EitherT.right(rand(seed)).flatMap { nr => + * if (p(nr)) + * EitherT.leftT("Predicate was false") + * else + * EitherT.pure(nr) + * } + * }}} + * + * @define monadErrorF For example data types like `cats.effect.IO` implement + * [[MonadError]] and can thus express computations that can fail on their + * own, usually via `Throwable`. When working with `EitherT`, if the + * underlying `F` is implementing [[ApplicativeError]], sometimes it is + * necessary to recover from such errors thrown in `F[_]`. */ 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)) + /** + * Folds the underlying `Either` value, yielding an `F[_]` as result. + * + * Example: + * {{{ + * val parsed: Eval[Long] = parseNum(num).fold(_ => 0, x => x) + * }}} + */ + 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) + /** + * Queries the underlying `Either` value, returning `true` if it's a `Left`, + * or `false` otherwise. + * + * {{{ + * // Yields `false` on evaluation + * EitherT.rightT[Eval, Throwable](10).isLeft + * + * // Yields `true` on evaluation + * EitherT.leftT[Eval, Int]("My error").isLeft + * }}} + * + * @see [[isRight]] + */ + def isLeft(implicit F: Functor[F]): F[Boolean] = + F.map(value)(_.isLeft) - def isRight(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.isRight) + /** + * Queries the underlying `Either` value, returning `true` if it's a `Right`, + * or `false` otherwise. + * + * {{{ + * // Yields `true` on evaluation + * EitherT.rightT[Eval, String](10).isRight + * + * // Yields `false` on evaluation + * EitherT.leftT[Eval, Int]("My error").isRight + * }}} + * + * @see [[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)) + /** + * Swaps the left and right values on evaluation, such that if the + * underlying `Either` is a `Left`, then returns a `Right` on the evaluation + * of `F[_]` or vice versa. + * + * {{{ + * val rt = EitherT.rightT[Eval, String](10) + * + * rt.value.value + * //=> res: Either[String,Int] = Right(10) + * + * rt.swap.value.value + * //=> res: Either[Int,String] = Left(10) + * }}} + */ + def swap(implicit F: Functor[F]): EitherT[F, B, A] = + EitherT(F.map(value)(_.swap)) - def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = F.map(value)(_.getOrElse(default)) + /** + * Casts the left type parameter to a super type. + * + * Like many types described in Cats, the type parameters of `EitherT` + * are invariant, even if the `Either` data type is covariant in both + * type parameters. + * + * This operator is a shortcut for safely upcasting the left type parameter + * to a compatible super type, an operation that would be automatic under + * Scala's type system if the parameters were declared as being covariant. + * + * Example: + * + * {{{ + * val num: EitherT[Eval, NumberFormatException, Long] = + * EitherT.rightT(10210) + * + * val num2: EitherT[Eval, Throwable, Long] = + * num.upcastL + * }}} + * + * @see [[upcastR]] + */ + @inline def upcastL[AA >: A]: EitherT[F, AA, B] = + this.asInstanceOf[EitherT[F, AA, B]] - def getOrElseF[BB >: B](default: => F[BB])(implicit F: Monad[F]): F[BB] = { + /** + * Casts the right type parameter to a super type. + * + * Like many types described in Cats, the type parameters of `EitherT` + * are invariant, even if the `Either` data type is covariant in both + * type parameters. + * + * This operator is a shortcut for safely upcasting the right type parameter + * to a compatible super type, an operation that would be automatic under + * Scala's type system if the parameters were declared as being covariant. + * + * {{{ + * val list: EitherT[Eval, String, List[Int]] = + * EitherT.rightT(List(10)) + * + * val iter: EitherT[Eval, String, Iterable[Int]] = + * list.upcastR + * }}} + * + * @see [[upcastL]] + */ + @inline def upcastR[BB >: B]: EitherT[F, A, BB] = + this.asInstanceOf[EitherT[F, A, BB]] + + /** + * If the underlying `Either` is a `Right`, then returns its value in + * the `F[_]` context, otherwise returns `default`. + * + * {{{ + * val num1: EitherT[Eval, String, Long] = + * EitherT.rightT(10) + * + * // Yields 10 + * num1.getOrElse(0L) + * + * val num2: EitherT[Eval, String, Long] = + * EitherT.leftT("Invalid number") + * + * // Yields 0 + * num2.getOrElse(0L) + * }}} + * + * @see [[getOrElseF]] and [[orElse]] + * + * @param default is a thunk to evaluate in case the underlying + * `Either` produced by `F[_]` is a `Left` value + */ + def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = + F.map(value)(_.getOrElse(default)) + + /** + * If the underlying `Either` is a `Right`, then returns its value in + * the `F[_]` context, otherwise evaluate `default` instead. + * + * {{{ + * val num1: EitherT[Eval, String, Long] = + * EitherT.rightT(10) + * + * // Yields 10 + * num1.getOrElseF(Eval.now(0L)) + * + * val num2: EitherT[Eval, String, Long] = + * EitherT.leftT("Invalid number") + * + * // Yields Long.MaxValue + * num2.getOrElseF(Eval.always(Long.MinValue - 1)) + * }}} + * + * @see [[getOrElse]] and [[orElse]] + * + * @param default is a thunk to evaluate in case the underlying + * `Either` produced by `F[_]` is a `Left` value + */ + def getOrElseF[BB >: B](default: => F[BB])(implicit F: Monad[F]): F[BB] = F.flatMap(value) { - case Left(_) => default case Right(b) => F.pure(b) + // N.B. pattern match does not use `case Left(_)` on purpose + case _ => default } - } - def orElse[AA, BB >: B](default: => EitherT[F, AA, BB])(implicit F: Monad[F]): EitherT[F, AA, BB] = { + /** + * Returns the result of the source unchanged if the underlying `Either` + * is evaluated to a `Right` valued, otherwise evaluates the given + * `default` thunk. + * + * {{{ + * def randEven(seed: Long): EitherT[Eval, String, Int] = + * randPredicate(seed)(_ % 2 == 0) orElse randEven(seed + 1) + * }}} + */ + def orElse[AA, BB >: B](default: => EitherT[F, AA, BB])(implicit F: Monad[F]): EitherT[F, AA, BB] = EitherT(F.flatMap(value) { - case Left(_) => default.value - case r @ Right(_) => F.pure(r.leftCast) + case r @ Right(_) => F.pure(eitherCast(r)) + // N.B. pattern match does not use `case Left(_)` on purpose + case _ => default.value }) - } + /** + * Handles errors by materializing them into `Either` values. + * + * This is the implementation of [[ApplicativeError.attempt]]. + * + * Example: + * {{{ + * parseNum(x).attempt.map { + * case Right(num) => num + * case Left(_) => 0L + * } + * }}} + * + * @see [[attemptF]] for recovering errors thrown in `F[_]` + * + * @see [[redeem]] and [[redeemWith]] for optimizations on `attempt.map` + * and `attempt.flatMap` respectively + */ + def attempt(implicit F: Functor[F]): EitherT[F, A, Either[A, B]] = + EitherT.right(value) + + /** + * Handles errors thrown in `F[_]` (if it implements [[ApplicativeError]]), + * by materializing them into `Either` values. + * + * $monadErrorF + * + * Example: + * {{{ + * import cats.effect.IO + * + * val rio = EitherT.pure[IO, String](10) + * + * // Yields Right(10) + * rio.attemptF + * + * val dummy = new RuntimeException("dummy") + * val lio = EitherT[IO, String, Int](IO.raiseError(dummy)) + * + * // Yields Left(RuntimeException("dummy")) + * lio.attemptF + * }}} + * + * Note that in this sample we are materializing the `Throwable` of the + * underlying `IO`, even the source `EitherT` value is not parametrized + * with it. + * + * @see [[attempt]] for recovering errors expressed via `EitherT` + * + * @see [[redeemF]] and [[redeemWithF]] for optimizations on `attemptF.map` + * and `attemptF.flatMap` respectively + */ + def attemptF[E](implicit F: ApplicativeError[F, E]): EitherT[F, A, Either[E, B]] = + EitherT(F.map(F.attempt(value)) { + case error @ Left(_) => + Right(eitherCast(error)) + case Right(eitherB) => + eitherB match { + case right @ Right(_) => Right(eitherCast(right)) + // N.B. pattern match does not do `case Left(_)` on purpose + case _ => eitherCast(eitherB) + } + }) + + /** + * Handle any error, potentially recovering from it, by mapping it via + * the provided function. + * + * This is the implementation of [[ApplicativeError.handleError]]. + * + * Example: + * {{{ + * parseNum(s).handleError(_ => 0L) + * }}} + * + * @see [[handleErrorWith]] for recovering errors by mapping them to + * `EitherT` values (aka the equivalent of `flatMap` for errors) + * + * @see [[handleErrorF]] for recovering errors thrown in `F[_]` + */ + def handleError(f: A => B)(implicit F: Functor[F]): EitherT[F, A, B] = + EitherT(F.map(value) { + case Left(a) => Right(f(a)) + // N.B. pattern match does not do `case Right(_)` on purpose + case right => right + }) + + /** + * Handles any error in `F[_]`, potentially recovering from it, by mapping + * it via the provided function. + * + * $monadErrorF + * + * Example: + * {{{ + * import cats.effect.IO + * import java.lang.Long.{parseNum => javaParseNum} + * + * def parseNum(s: String, r: Int = 10): IO[Long] = + * IO(javaParseNum(s, r)) + * + * val eio = EitherT.right[String](parseNum("invalid")) + * + * // Yields 0L on evaluation + * eio.handleErrorF(_ => 0L) + * }}} + * + * @see [[handleErrorWithF]] for recovering errors by mapping them to + * `EitherT` values (aka the equivalent of `flatMap` for errors) + * + * @see [[handleError]] for recovering errors expressed via `EitherT` + */ + def handleErrorF[E](f: E => B)(implicit F: ApplicativeError[F, E]): EitherT[F, A, B] = + EitherT(F.handleError(value)(e => Right(f(e)))) + + /** + * Handle any error, potentially recovering from it, by mapping it via + * the provided function to another `EitherT` value. + * + * This is the implementation of [[ApplicativeError.handleErrorWith]]. + * + * Example: + * {{{ + * import cats.Eval + * import java.lang.Long.{parseNum => javaParseNum} + * + * def parseNum(s: String, r: Int = 10): EitherT[Eval, String, Long] = + * EitherT(Eval.always { + * try + * Right(javaParseNum(s, r)) + * catch { case _: NumberFormatException => + * Left("invalid number") + * } + * }) + * + * // Yields 10210 because there's no error here + * parseNum("10210").handleErrorWith(_ => EitherT.pure(0L)) + * + * parseNum("Hello").handleErrorWith { + * case "invalid number" => + * EitherT.pure(0L) + * case other => + * // Rethrowing error, because we don't know what it is + * EitherT.leftT(other) + * } + * }}} + * + * @see [[handleError]] for recovering errors by mapping them to simple values + * + * @see [[handleErrorWithF]] for recovering errors thrown in `F[_]` + */ + def handleErrorWith(f: A => EitherT[F, A, B])(implicit F: Monad[F]): EitherT[F, A, B] = + EitherT(F.flatMap(value) { + case Left(a) => f(a).value + // N.B. pattern match does not do `case Right(_)` on purpose + case right => F.pure(right) + }) + + /** + * Handles any error in `F[_]`, potentially recovering from it, by mapping + * it via the provided function to another `EitherT` value. + * + * $monadErrorF + * + * Example: + * {{{ + * import cats.effect.IO + * import java.lang.Long.{parseNum => javaParseNum} + * + * def parseNum(s: String, r: Int = 10): IO[Long] = + * IO(javaParseNum(s, r)) + * + * val eio = EitherT.right[String](parseNum("invalid")) + * + * // Yields 0L on evaluation + * eio.handleErrorWithF { + * case _: NumberFormatException => + * EitherT.pure(0L) + * case other => + * // We are only recovering from NumberFormatException here because we + * // don't know of other exceptions that could be thrown and thus we + * // prefer to treat them as unrecoverable + * EitherT.right(IO.raiseError(other)) + * } + * }}} + * + * @see [[handleErrorF]] for recovering errors by mapping them to simple values + * + * @see [[handleErrorWith]] for recovering errors expressed via `EitherT` + */ + def handleErrorWithF[E](f: E => EitherT[F, A, B])(implicit F: ApplicativeError[F, E]): EitherT[F, A, B] = + EitherT(F.handleErrorWith(value)(f(_).value)) + + /** + * Handle any error, potentially recovering from it, by mapping it via + * the provided partial function. In case the provided partial function + * isn't defined for the given input, then the error is rethrown. + * + * This is the implementation of [[ApplicativeError.recover]]. + * + * Example: + * {{{ + * parseNum(s).recover { + * case "invalid number" => 0L + * } + * }}} + * + * @see [[handleError]] for handling all errors via a total function + * + * @see [[recoverF]] for handling errors thrown in `F[_]` + */ def recover(pf: PartialFunction[A, B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(value)(_.recover(pf))) + /** + * Handles any error in `F[_]`, potentially recovering from it, by mapping + * it via the provided partial function, with unhandled errors being + * re-thrown in the `F[_]` context. + * + * $monadErrorF + * + * Example: + * {{{ + * import cats.effect.IO + * import java.lang.Long.{parseNum => javaParseNum} + * + * def parseNum(s: String, r: Int = 10): IO[Long] = + * IO(javaParseNum(s, r)) + * + * val eio = EitherT.right[String](parseNum("invalid")) + * + * // Yields 0L on evaluation + * eio.recoverF { + * case _: NumberFormatException => 0L + * } + * }}} + * + * @see [[handleErrorF]] for recovering all errors via a total function + * + * @see [[recover]] for recovering errors expressed via `EitherT` + */ + def recoverF[E](pf: PartialFunction[E, B])(implicit F: ApplicativeError[F, E]): EitherT[F, A, B] = + EitherT(F.recover(value)(pf.andThen(rightBox))) + + /** + * Handle any error, potentially recovering from it, by mapping it via + * the provided partial function to another `EitherT` value, with + * unhandled errors being re-thrown in the `EitherT` context. + * + * This is the implementation of [[ApplicativeError.recoverWith]]. + * + * Example: + * {{{ + * import cats.Eval + * import java.lang.Long.{parseNum => javaParseNum} + * + * def parseNum(s: String, r: Int = 10): EitherT[Eval, String, Long] = + * EitherT(Eval.always { + * try + * Right(javaParseNum(s, r)) + * catch { case _: NumberFormatException => + * Left("invalid number") + * } + * }) + * + * parseNum("Hello").recoverWith { + * case "invalid number" => + * EitherT.pure(0L) + * } + * }}} + * + * @see [[handleErrorWith]] for recovering all errors via a total function + * + * @see [[recoverWithF]] for recovering errors thrown in `F[_]` + */ def recoverWith(pf: PartialFunction[A, EitherT[F, A, B]])(implicit F: Monad[F]): EitherT[F, A, B] = EitherT(F.flatMap(value) { case Left(a) if pf.isDefinedAt(a) => pf(a).value case other => F.pure(other) }) - def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = fold(f, identity) + /** + * Handles errors in `F[_]`, potentially recovering from them, by mapping + * errors via the provided partial function to other `EitherT` values, + * unhandled errors being rethrown in the `F[_]` context. + * + * $monadErrorF + * + * Example: + * {{{ + * import cats.effect.IO + * import java.lang.Long.{parseNum => javaParseNum} + * + * def parseNum(s: String, r: Int = 10): IO[Long] = + * IO(javaParseNum(s, r)) + * + * val eio = EitherT.right[String](parseNum("invalid")) + * + * // Yields 0L on evaluation + * eio.recoverWithF { + * case _: NumberFormatException => + * EitherT.pure(0L) + * } + * }}} + * + * @see [[handleErrorWithF]] for recovering errors via a total function + * + * @see [[recoverWith]] for recovering errors expressed via `EitherT` + */ + def recoverWithF[E](pf: PartialFunction[E, EitherT[F, A, B]])(implicit F: ApplicativeError[F, E]): EitherT[F, A, B] = + EitherT(F.recoverWith(value)(pf.andThen(_.value))) - def valueOrF[BB >: B](f: A => F[BB])(implicit F: Monad[F]): F[BB] = { - F.flatMap(value){ - case Left(a) => f(a) - case Right(b) => F.pure(b) - } - } + /** + * Given a pair of functions, transforms the underlying `Either` value + * to a (successful) `Right` result. + * + * This is the implementation of [[MonadError.redeem]] and is an operation + * that can be derived from `attempt.map`, being an optimization on it: + * + * {{{ + * et.redeem(recover, map) <-> et.attempt.map(_.fold(recover, map)) + * }}} + * + * Example: + * {{{ + * parseNum(x).redeem( + * // error recovery + * _ => 0L, + * num => num + * ) + * }}} + * + * @see [[attempt]] for materializing errors + * + * @see [[redeemF]] for redeeming errors thrown in the `F[_]` context + */ + def redeem[R](recover: A => R, map: B => R)(implicit F: Functor[F]): EitherT[F, A, R] = + EitherT(F.map(value) { + case Right(b) => Right(map(b)) + case Left(a) => Right(recover(a)) + }) - def forall(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.forall(f)) + /** + * Returns a new `EitherT` value that transforms the result of the source, + * given the `recover` or `map` functions, which get executed depending + * on whether the underlying `F[_]` is successful or if it ends in error. + * + * $monadErrorF + * + * Example: + * {{{ + * import cats.effect.IO + * + * val rio = EitherT.pure[IO, String](10) + * + * val dummy = new RuntimeException("dummy") + * val lio = EitherT[IO, String, Int](IO.raiseError(dummy)) + * + * // Yields 0L on evaluation + * lio.redeemF( + * error => 0L, + * num => num) + * }}} + * + * Note that in this sample we are recovering from the `Throwable` of the + * underlying `IO`, even the source `EitherT` value is not parametrized + * with it. + * + * This function is an optimization on usage of [[attemptF]] and `map`, + * as this equivalence always holds: + * + * {{{ + * et.redeemF(fe, fb) <-> et.attemptF.map(_.fold(fe, fb)) + * }}} + * + * @see [[attemptF]] for materialized errors thrown in the `F[_]` context + * + * @see [[redeem]] for recovering errors expressed via `EitherT` + */ + def redeemF[E, R](recover: E => R, map: B => R)(implicit F: MonadError[F, E]): EitherT[F, A, R] = + EitherT(F.redeem(value)(e => Right(recover(e)), { + case Right(a) => Right(map(a)) + case error => eitherCast(error) + })) - def exists(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.exists(f)) + /** + * Returns a new value that transforms the result of the source, + * given the `recover` or `bind` functions, which get executed depending + * on whether the underlying `Either` value is a `Left` or a `Right`. + * + * This is the implementation of [[MonadError.redeemWith]] and is an operation + * that can be derived from `attempt.flatMap`, being an optimization on it: + * + * {{{ + * et.redeemWith(recover, map) <-> et.attempt.flatMap(_.fold(recover, map)) + * }}} + * + * Example: + * {{{ + * parseNum(x).redeemWith( + * // error recovery + * error => error match { + * case "invalid number" => EitherT.pure(0L) + * case other => + * // Rethrowing unknown error types + * EitherT.leftT(other) + * }, + * num => + * // Binding to another value; we could do something more + * // fancy here, like an actual bind continuation + * EitherT.pure(num) + * ) + * }}} + * + * @see [[attempt]] for materializing errors + * + * @see [[redeemWithF]] for redeeming errors thrown in the `F[_]` context + */ + def redeemWith[R](recover: A => EitherT[F, A, R], bind: B => EitherT[F, A, R])(implicit F: Monad[F]): EitherT[F, A, R] = + EitherT(F.flatMap(value) { + case Right(b) => bind(b).value + case Left(a) => recover(a).value + }) + + /** + * Returns a new `EitherT` value that transforms the result of the source, + * given the `recover` or `bind` functions, which get executed depending + * on whether the underlying `F[_]` is successful or if it ends in error. + * + * $monadErrorF + * + * Example: + * {{{ + * import cats.effect.IO + * + * val rio = EitherT.pure[IO, String](10) + * + * val dummy = new RuntimeException("dummy") + * val lio = EitherT[IO, String, Int](IO.raiseError(dummy)) + * + * // Yields 0L on evaluation + * lio.redeemWithF( + * // error recovery + * error => error match { + * case `dummy` => EitherT.pure(0L) + * case other => + * // Rethrowing other errors we don't recognize + * EitherT(IO.raiseError(other)) + * }, + * num => { + * // Binding to another value; we could do something more + * // fancy here, like an actual bind continuation + * EitherT.pure(num) + * }) + * }}} + * + * Note that in this sample we are recovering from the `Throwable` of the + * underlying `IO`, even the source `EitherT` value is not parametrized + * with it. + * + * This function is an optimization on usage of [[attemptF]] and `flatMap`, + * as this equivalence always holds: + * + * {{{ + * et.redeemWithF(fe, fb) <-> et.attemptF.flatMap(_.fold(fe, fb)) + * }}} + * + * @see [[attemptF]] for materialized errors thrown in the `F[_]` context + * + * @see [[redeemWith]] for recovering errors expressed via `EitherT` + */ + def redeemWithF[E, R](recover: E => EitherT[F, A, R], bind: B => EitherT[F, A, R])(implicit F: MonadError[F, E]): EitherT[F, A, R] = + EitherT(F.redeemWith(value)(e => recover(e).value, { + case Right(a) => bind(a).value + case error => F.pure(eitherCast(error)) + })) + + /** + * Returns the `Right` value in `F[_]`, or transforms the `Left` value + * via the provided function. + * + * {{{ + * val et1 = EitherT.right[String](Eval.always(10)) + * // Yields 10 + * et1.valueOr(_ => 0L) + * + * val et2 = EitherT.left[int](Eval.always("error")) + * // Yields 0 + * et2.valueOr(_ => 0L) + * }}} + */ + def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = + fold(f, identity) + /** + * Returns the `Right` value in `F[_]`, or transforms the `Left` value + * via the provided function that processes the final result in `F[_]`. + * + * {{{ + * val et1 = EitherT.right[String](Eval.always(10)) + * // Yields 10 + * et1.valueOrF(_ => parseNum("0")) + * + * val et2 = EitherT.left[int](Eval.always("error")) + * // Yields 0 + * et2.valueOrF(_ => parseNum("0")) + * }}} + */ + def valueOrF[BB >: B](f: A => F[BB])(implicit F: Monad[F]): F[BB] = + F.flatMap(value){ + case Left(a) => f(a) + case Right(b) => F.pure(b) + } + + /** + * Returns `true` if `Left` or returns the result of the application of + * the given predicate function to the `Right` value. + */ + def forall(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = + F.map(value)(_.forall(f)) + + /** + * Returns `false` if `Left` or returns the result of the given predicate + * function applied to the `Right` value. + */ + def exists(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = + F.map(value)(_.exists(f)) + + /** + * Turns a successful value into an error if it does not satisfy the given + * predicate. + * + * This is the implementation of [[MonadError.ensure]]. + */ def ensure[AA >: A](onFailure: => AA)(f: B => Boolean)(implicit F: Functor[F]): EitherT[F, AA, B] = EitherT(F.map(value)(_.ensure(onFailure)(f))) + /** + * Turns a successful value into an error specified by the `onFailure` + * function if it does not satisfy a given predicate. + * + * This is the implementation of [[MonadError.ensureOr]]. + */ def ensureOr[AA >: A](onFailure: B => AA)(f: B => Boolean)(implicit F: Functor[F]): EitherT[F, AA, B] = EitherT(F.map(value)(_.ensureOr(onFailure)(f))) - def toOption(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.toOption)) + /** + * Converts this `EitherT` to [[OptionT]], `Right` values being converted + * to `Some` and `Left` values being converted to `None`. + */ + def toOption(implicit F: Functor[F]): OptionT[F, B] = + OptionT(F.map(value)(_.toOption)) + /** + * Given a `G[_]` type parameter that implements [[Alternative]], converts + * this `EitherT` value to it, keeping the `F[_]` context. + * + * `Alternative` is basically [[MonoidK]] with [[Applicative]], meaning + * that `Left` values get translated to [[MonoidK.empty]] and `Right` values + * get translated to [[Applicative.pure]]. + * + * For example: + * + * {{{ + * val num = EitherT.right[String](Eval.always(1)) + * + * val numOpt: Eval[Option[Int]] = num.to[Option] + * numOpt.value + * //=> Some(1) + * + * val numList: Eval[List[Int]] = num.to[List] + * numList.value + * //=> List(1) + * + * val err = EitherT.left[Int](Eval.always("error")) + * + * val errOpt: Eval[Option[Int]] = err.to[Option] + * errOpt.value + * //=> None + * + * val errList: Eval[List[Int]] = err.to[List] + * errList.value + * //=> List() + * }}} + */ def to[G[_]](implicit F: Functor[F], G: Alternative[G]): F[G[B]] = F.map(value)(_.to[G]) + /** + * Given ab `F[_]` that implements [[Alternative]], collect all + * `Right` values or convert `Left` values to [[MonoidK.empty empty]]. + * + * Example: + * {{{ + * def validateNum(num: Int): Either[String, Int] = + * if (num % 2 == 0) Right(num) + * else Left("number is odd") + * + * val et: EitherT[List, String, Int] = + * EitherT((0 until 1000).toList.map(validateNum)) + * + * // Yields 0, 2, 4 ... 98 + * val evens: List[Int] = et.collectRight + * }}} + */ def collectRight(implicit FA: Alternative[F], FM: Monad[F]): F[B] = FM.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))) + /** + * The `bimap` operation can apply a transformation to each "side" of + * the underlying `Either` value. + * + * This is the [[Bifunctor.bimap]] implementation. It's very much like + * normal [[map]], except that it can also transform the left side as well. + * + * Example: + * {{{ + * val et1: EitherT[Eval, String, Int] = + * EitherT.leftT("12012") + * + * // Yields Left(12012 : Int) + * val et2: EitherT[Eval, Int, String] = + * et1.bimap( + * left => left.toInt, + * right => right.toString) + * + * // Yields Left(23123) + * val et3: EitherT[Eval, Int, Int] = + * et2.bimap(a => a + 11111, _.toInt) + * }}} + * + * @param fa is the mapping function applied to `Left` values + * @param fb is the mapping function applied to `Right` values + */ + 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))) + /** + * Traverse each "side" of the structure with the given functions. + * + * Implements [[Bitraverse.bitraverse]]. + */ 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) + /** + * Given a mapping function in the `EitherT[F, A, ?]` context, applies it + * to `Right` values. + * + * This is very much like [[Applicative.ap]], except it's made to work with + * the right side, as needed by a bifunctor like `EitherT`. + */ 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))) + /** + * The monadic bind. + * + * Implements [[FlatMap.flatMap]]. + * + * Monad transforms like `EitherT` are essentially built to provide a `flatMap` + * implementation that reuses the underlying `F.flatMap` operation, hiding + * the complexity involved (in this case hiding the plumbing needed for + * dealing with `Left` values). + * + * @see [[flatMapF]] for mapping functions returning values in the + * `F[_]` context + */ def flatMap[AA >: A, D](f: B => EitherT[F, AA, D])(implicit F: Monad[F]): EitherT[F, AA, D] = EitherT(F.flatMap(value) { - case l @ Left(_) => F.pure(l.rightCast) case Right(b) => f(b).value + // N.B. pattern match doesn't do `case Left(_)` on purpose + case left => F.pure(eitherCast(left)) }) + /** + * A monadic bind variant that takes a parameter a mapping function that + * works with `F[_]` return types, instead of `EitherT`. + * + * It has the same properties as normal [[flatMap]], except that it accepts + * functions whose output isn't wrapped in `EitherT` already. + */ def flatMapF[AA >: A, D](f: B => F[Either[AA, D]])(implicit F: Monad[F]): EitherT[F, AA, D] = - flatMap(f andThen EitherT.apply) + flatMap(b => EitherT(f(b))) + /** + * Maps the underlying `F[_]` value with the provided function. + * + * This is a shorthand for mapping the [[value]] directly: + * {{{ + * et.transform(f) <-> EitherT(et.value.map(f)) + * }}} + */ def transform[C, D](f: Either[A, B] => Either[C, D])(implicit F: Functor[F]): EitherT[F, C, D] = EitherT(F.map(value)(f)) + /** + * Applies `flatMap` to the underlying `Either` value. + * + * Notice the equivalence: + * {{{ + * et.subflatMap(f) <-> EitherT(et.value.map(_.flatMap(f))) + * }}} + */ def subflatMap[AA >: A, D](f: B => Either[AA, D])(implicit F: Functor[F]): EitherT[F, AA, D] = transform(_.flatMap(f)) - def map[D](f: B => D)(implicit F: Functor[F]): EitherT[F, A, D] = bimap(identity, f) + /** + * Given the mapping function, returns a new `EitherT` that transform + * the `Right` values of the source with it. + * + * Implements [[Functor.map]]. + */ + def map[D](f: B => D)(implicit F: Functor[F]): EitherT[F, A, D] = + EitherT(F.map(value) { + case Right(b) => Right(f(b)) + case left => eitherCast(left) + }) /** * Modify the context `F` using transformation `f`. */ def mapK[G[_]](f: F ~> G): EitherT[G, A, B] = EitherT[G, A, B](f(value)) + /** + * Maps `Right` values generated by the source using the provided function, + * whose output is given in the `F[_]` context. + * + * This operation resembles [[flatMap]] or [[flatMapF]], except that the + * returned value is wrapped via [[EitherT.right]] so it lacks the ability + * of yielding `Left` values, being more like [[map]] in that sense. + * + * Example: + * {{{ + * def parseInt(n: String): EitherT[Eval, String, Int] = + * EitherT(Eval.always { + * try Right(n.toInt) + * catch { case NonFatal(_) => Left("invalid number") } + * }) + * + * // Yields Right(10000) on evaluation + * parseInt("9999").semiflatMap { n => + * Eval.always(n + 1) + * } + * }}} + */ def semiflatMap[D](f: B => F[D])(implicit F: Monad[F]): EitherT[F, A, D] = flatMap(b => EitherT.right(f(b))) - def leftMap[C](f: A => C)(implicit F: Functor[F]): EitherT[F, C, B] = bimap(f, identity) + /** + * Applies the mapping function to the left "side". + * + * Like [[map]], except that it operates on `Left` values. + * + * @see [[bimap]] for mapping both sides + */ + def leftMap[C](f: A => C)(implicit F: Functor[F]): EitherT[F, C, B] = + bimap(f, identity) + /** + * Binds the source to a new `EitherT` value using the provided function + * that operates on the left "side". + * + * This is like [[flatMap]] except that it operates on `Left` values, + * needed because `EitherT` is a bifunctor. + */ def leftFlatMap[BB >: B, D](f: A => EitherT[F, D, BB])(implicit F: Monad[F]): EitherT[F, D, BB] = EitherT(F.flatMap(value) { case Left(a) => f(a).value - case r@Right(_) => F.pure(r.leftCast) + // N.B. pattern match doesn't do `case Right(_)` on purpose + case right => F.pure(eitherCast(right)) }) + /** + * Maps `Left` values generated by the source using the provided function, + * whose output is given in the `F[_]` context. + * + * This is like [[semiflatMap]] except that it operates on `Left` values, + * needed because `EitherT` is a bifunctor. + */ def leftSemiflatMap[D](f: A => F[D])(implicit F: Monad[F]): EitherT[F, D, B] = EitherT(F.flatMap(value) { case Left(a) => F.map(f(a)) { d => Left(d) } - case r@Right(_) => F.pure(r.leftCast) + // N.B. pattern match doesn't do `case Right(_)` on purpose + case right => F.pure(eitherCast(right)) }) def compare(that: EitherT[F, A, B])(implicit o: Order[F[Either[A, B]]]): Int = @@ -138,7 +1073,8 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C])(implicit F: Foldable[F]): Eval[C] = F.foldRight(value, lc)((axb, lc) => axb.foldRight(lc)(f)) - def merge[AA >: A](implicit ev: B <:< AA, F: Functor[F]): F[AA] = F.map(value)(_.fold(identity, ev.apply)) + def merge[AA >: A](implicit ev: B <:< AA, F: Functor[F]): F[AA] = + F.map(value)(_.fold(identity, ev.apply)) /** * Similar to `Either#combine` but mapped over an `F` context. @@ -206,7 +1142,8 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { 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) + 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]`. @@ -229,7 +1166,8 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { * }}} * */ - def toNested: Nested[F, Either[A, ?], B] = Nested[F, Either[A, ?], B](value) + def toNested: Nested[F, Either[A, ?], B] = + Nested[F, Either[A, ?], B](value) /** * Transform this `EitherT[F, A, B]` into a `[[Nested]][F, Validated[A, ?], B]`. @@ -264,7 +1202,7 @@ object EitherT extends EitherTInstances { * Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics. */ private[data] final class LeftPartiallyApplied[B](val dummy: Boolean = true) extends AnyVal { - def apply[F[_], A](fa: F[A])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fa)(Either.left)) + def apply[F[_], A](fa: F[A])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fa)(leftBox)) } /** @@ -300,7 +1238,7 @@ object EitherT extends EitherTInstances { * Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics. */ private[data] final class RightPartiallyApplied[A](val dummy: Boolean = true) extends AnyVal { - def apply[F[_], B](fb: F[B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fb)(Either.right)) + def apply[F[_], B](fb: F[B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fb)(rightBox)) } /** @@ -588,25 +1526,33 @@ private[data] trait EitherTMonad[F[_], L] extends Monad[EitherT[F, L, ?]] with E private[data] trait EitherTMonadErrorF[F[_], E, L] extends MonadError[EitherT[F, L, ?], E] with EitherTMonad[F, L] { implicit val F: MonadError[F, E] - def handleErrorWith[A](fea: EitherT[F, L, A])(f: E => EitherT[F, L, A]): EitherT[F, L, A] = - EitherT(F.handleErrorWith(fea.value)(f(_).value)) - - def raiseError[A](e: E): EitherT[F, L, A] = EitherT(F.raiseError(e)) + override def handleError[A](fea: EitherT[F, L, A])(f: E => A): EitherT[F, L, A] = + fea.handleErrorF(f) + override def handleErrorWith[A](fea: EitherT[F, L, A])(f: E => EitherT[F, L, A]): EitherT[F, L, A] = + fea.handleErrorWithF(f) + override def attempt[A](fea: EitherT[F, L, A]): EitherT[F, L, Either[E, A]] = + fea.attemptF + override def redeem[A, B](fea: EitherT[F, L, A])(fe: E => B, fs: A => B): EitherT[F, L, B] = + fea.redeemF(fe, fs) + override def redeemWith[A, B](fea: EitherT[F, L, A])(fe: E => EitherT[F, L, B], fs: A => EitherT[F, L, B]): EitherT[F, L, B] = + fea.redeemWithF(fe, fs) + override def raiseError[A](e: E): EitherT[F, L, A] = + EitherT(F.raiseError(e)) } 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 handleError[A](fla: EitherT[F, L, A])(f: L => A): EitherT[F, L, A] = + fla.handleError(f) + def handleErrorWith[A](fla: EitherT[F, L, A])(f: L => EitherT[F, L, A]): EitherT[F, L, A] = + fla.handleErrorWith(f) + override def redeem[A, B](fla: EitherT[F, L, A])(recover: L => B, map: A => B): EitherT[F, L, B] = + fla.redeem(recover, map) + override def redeemWith[A, B](fla: EitherT[F, L, A])(recover: L => EitherT[F, L, B], bind: A => EitherT[F, L, B]): EitherT[F, L, B] = + fla.redeemWith(recover, bind) + override def raiseError[A](e: L): EitherT[F, L, A] = + EitherT(F.pure(Left(e))) + override def attempt[A](fla: EitherT[F, L, A]): EitherT[F, L, Either[L, A]] = + fla.attempt 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] = diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index 97d6afa780..d7889713bf 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -4,7 +4,7 @@ package data import cats.Bifunctor import cats.arrow.FunctionK import cats.data.Validated.{Invalid, Valid} - +import cats.internals.EitherUtil.{rightBox, leftBox} import scala.annotation.tailrec /** Represents a right-biased disjunction that is either an `A`, or a `B`, or both an `A` and a `B`. @@ -49,7 +49,7 @@ sealed abstract class Ior[+A, +B] extends Product with Serializable { 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: Either[Either[A, B], (A, B)] = fold(a => Left(Left(a)), b => Left(Right(b)), (a, b) => Right((a, b))) - final def toEither: Either[A, B] = fold(Left(_), Right(_), (_, b) => Right(b)) + final def toEither: Either[A, B] = fold(leftBox, rightBox, (_, b) => Right(b)) final def toValidated: Validated[A, B] = fold(Invalid(_), Valid(_), (_, b) => Valid(b)) final def toOption: Option[B] = right final def toList: List[B] = right.toList diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index c5adfab099..73f682c1e0 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -1,7 +1,7 @@ package cats package instances -import cats.syntax.EitherUtil +import cats.internals.EitherUtil.{leftBox, rightBox, eitherCast} import cats.syntax.either._ import scala.annotation.tailrec @@ -10,8 +10,8 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { new Bitraverse[Either] { def bitraverse[G[_], A, B, C, D](fab: Either[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[Either[C, D]] = fab match { - case Left(a) => G.map(f(a))(Left(_)) - case Right(b) => G.map(g(b))(Right(_)) + case Left(a) => G.map(f(a))(leftBox) + case Right(b) => G.map(g(b))(rightBox) } def bifoldLeft[A, B, C](fab: Either[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = @@ -30,53 +30,61 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { // scalastyle:off method.length implicit def catsStdInstancesForEither[A]: MonadError[Either[A, ?], A] with Traverse[Either[A, ?]] = new MonadError[Either[A, ?], A] with Traverse[Either[A, ?]] { - def pure[B](b: B): Either[A, B] = Right(b) + override 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) + override def flatMap[B, C](fa: Either[A, B])(f: B => Either[A, C]): Either[A, C] = + fa match { + case Right(b) => f(b) + case _ => eitherCast(fa) + } - def handleErrorWith[B](fea: Either[A, B])(f: A => Either[A, B]): Either[A, B] = + override 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 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) + fa match { + case Right(b) => Right(f(b)) + case _ => eitherCast(fa) + } @tailrec - def tailRecM[B, C](b: B)(f: B => Either[A, Either[B, C]]): Either[A, C] = + override def tailRecM[B, C](b: B)(f: B => Either[A, Either[B, C]]): Either[A, C] = f(b) match { - case left @ Left(_) => - left.rightCast[C] case Right(e) => e match { case Left(b1) => tailRecM(b1)(f) - case right @ Right(_) => right.leftCast[A] + case _ => eitherCast(e) } + case left => + eitherCast(left) } override def map2Eval[B, C, Z](fb: Either[A, B], fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = fb match { - case l @ Left(_) => Now(EitherUtil.rightCast(l)) + case l @ Left(_) => Now(eitherCast(l)) case Right(b) => fc.map(_.right.map(f(b, _))) } - def traverse[F[_], B, C](fa: Either[A, B])(f: B => F[C])(implicit F: Applicative[F]): F[Either[A, C]] = + override def traverse[F[_], B, C](fa: Either[A, B])(f: B => F[C])(implicit F: Applicative[F]): F[Either[A, C]] = fa match { - case left @ Left(_) => F.pure(left.rightCast[C]) - case Right(b) => F.map(f(b))(Right(_)) + case left @ Left(_) => F.pure(eitherCast(left)) + case Right(b) => F.map(f(b))(rightBox) } - def foldLeft[B, C](fa: Either[A, B], c: C)(f: (C, B) => C): C = + override def foldLeft[B, C](fa: Either[A, B], c: C)(f: (C, B) => C): C = fa match { case Left(_) => c case Right(b) => f(c, b) } - def foldRight[B, C](fa: Either[A, B], lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = + override def foldRight[B, C](fa: Either[A, B], lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = fa match { case Left(_) => lc case Right(b) => f(b, lc) @@ -91,6 +99,12 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { override def recoverWith[B](fab: Either[A, B])(pf: PartialFunction[A, Either[A, B]]): Either[A, B] = fab recoverWith pf + override def redeem[B, R](fab: Either[A, B])(recover: A => R, map: B => R): Either[A, R] = + Right(fab.fold(recover, map)) + + override def redeemWith[B, R](fab: Either[A, B])(recover: A => Either[A, R], bind: B => Either[A, R]): Either[A, R] = + fab.fold(recover, bind) + override def fromEither[B](fab: Either[A, B]): Either[A, B] = fab diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index 2c62956816..83e6be877e 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -1,34 +1,37 @@ package cats package instances -import scala.util.control.NonFatal +import cats.internals.FutureShims import scala.concurrent.{ExecutionContext, Future} trait FutureInstances extends FutureInstances1 { implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] = new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] with StackSafeMonad[Future] { - def pure[A](x: A): Future[A] = Future.successful(x) - - def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) - - def handleErrorWith[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] = fea.recoverWith { case t => f(t) } - - 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 pure[A](x: A): Future[A] = + Future.successful(x) + override def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = + fa.flatMap(f) + override def handleErrorWith[A](fea: Future[A])(f: Throwable => Future[A]): Future[A] = + fea.recoverWith { case t => f(t) } + override 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[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) - - override def recoverWith[A](fa: Future[A])(pf: PartialFunction[Throwable, Future[A]]): Future[A] = fa.recoverWith(pf) - - override def map[A, B](fa: Future[A])(f: A => B): Future[B] = fa.map(f) - - override def catchNonFatal[A](a: => A)(implicit ev: Throwable <:< Throwable): Future[A] = Future(a) - - override def catchNonFatalEval[A](a: Eval[A])(implicit ev: Throwable <:< Throwable): Future[A] = Future(a.value) + FutureShims.attempt(fa) + override def redeemWith[A, B](fa: Future[A])(recover: Throwable => Future[B], bind: A => Future[B]): Future[B] = + FutureShims.redeemWith(fa)(recover, bind) + override def recover[A](fa: Future[A])(pf: PartialFunction[Throwable, A]): Future[A] = + fa.recover(pf) + override def recoverWith[A](fa: Future[A])(pf: PartialFunction[Throwable, Future[A]]): Future[A] = + fa.recoverWith(pf) + override def map[A, B](fa: Future[A])(f: A => B): Future[B] = + fa.map(f) + override def catchNonFatal[A](a: => A)(implicit ev: Throwable <:< Throwable): Future[A] = + Future(a) + override def catchNonFatalEval[A](a: Eval[A])(implicit ev: Throwable <:< Throwable): Future[A] = + Future(a.value) } } diff --git a/core/src/main/scala/cats/instances/option.scala b/core/src/main/scala/cats/instances/option.scala index 7ca772bc13..e220c5dd84 100644 --- a/core/src/main/scala/cats/instances/option.scala +++ b/core/src/main/scala/cats/instances/option.scala @@ -56,6 +56,20 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances { def handleErrorWith[A](fa: Option[A])(f: (Unit) => Option[A]): Option[A] = fa orElse f(()) + override def redeem[A, B](fa: Option[A])(recover: Unit => B, map: A => B): Option[B] = + fa match { + case Some(a) => Some(map(a)) + // N.B. not pattern matching `case None` on purpose + case _ => Some(recover(())) + } + + override def redeemWith[A, B](fa: Option[A])(recover: Unit => Option[B], bind: A => Option[B]): Option[B] = + fa match { + case Some(a) => bind(a) + // N.B. not pattern matching `case None` on purpose + case _ => recover(()) + } + def traverse[G[_]: Applicative, A, B](fa: Option[A])(f: A => G[B]): G[Option[B]] = fa match { case None => Applicative[G].pure(None) diff --git a/core/src/main/scala/cats/instances/try.scala b/core/src/main/scala/cats/instances/try.scala index 31f8698552..0b07b73822 100644 --- a/core/src/main/scala/cats/instances/try.scala +++ b/core/src/main/scala/cats/instances/try.scala @@ -68,7 +68,14 @@ trait TryInstances extends TryInstances1 { ta.recover { case t => f(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) } + ta match { case Success(a) => Success(Right(a)); case Failure(e) => Success(Left(e)) } + + override def redeem[A, B](ta: Try[A])(recover: Throwable => B, map: A => B): Try[B] = + ta match { case Success(a) => Try(map(a)); case Failure(e) => Try(recover(e)) } + + override def redeemWith[A, B](ta: Try[A])(recover: Throwable => Try[B], bind: A => Try[B]): Try[B] = + try ta match { case Success(a) => bind(a); case Failure(e) => recover(e) } + catch { case NonFatal(e) => Failure(e) } override def recover[A](ta: Try[A])(pf: PartialFunction[Throwable, A]): Try[A] = ta.recover(pf) diff --git a/core/src/main/scala/cats/internals/EitherUtil.scala b/core/src/main/scala/cats/internals/EitherUtil.scala new file mode 100644 index 0000000000..c75cddb2bf --- /dev/null +++ b/core/src/main/scala/cats/internals/EitherUtil.scala @@ -0,0 +1,35 @@ +package cats +package internals + +/** + * Internal API — Convenience functions for `Either`. + */ +private[cats] object EitherUtil { + @inline def leftCast[A, B, C](right: Right[A, B]): Either[C, B] = + right.asInstanceOf[Either[C, B]] + + @inline def rightCast[A, B, C](left: Left[A, B]): Either[A, C] = + left.asInstanceOf[Either[A, C]] + + @inline def eitherCast[A, B](either: Either[_, _]): Either[A, B] = + either.asInstanceOf[Either[A, B]] + + /** + * Internal API — reusable function for boxing values in `Right(_)`. + * To be used with `andThen`, e.g. `fa.map(f.andThen(rightBox))`. + */ + def rightBox[A, B]: B => Either[A, B] = + rightBoxRef.asInstanceOf[B => Either[A, B]] + + /** + * Internal API — reusable function for boxing values in `Left(_)`. + * To be used with `andThen`, e.g. `fa.map(f.andThen(leftBox))`. + */ + def leftBox[A, B]: A => Either[A, B] = + leftBoxRef.asInstanceOf[A => Either[A, B]] + + private[this] val rightBoxRef: Any => Either[Nothing, Nothing] = + a => new Right(a).asInstanceOf[Either[Nothing, Nothing]] + private[this] val leftBoxRef: Any => Either[Nothing, Nothing] = + a => new Left(a).asInstanceOf[Either[Nothing, Nothing]] +} diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index 40bd310652..da3662f6e2 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -5,6 +5,7 @@ import cats.data.{EitherT, Ior, NonEmptyList, Validated, ValidatedNel} import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} import EitherSyntax._ +import cats.internals.EitherUtil trait EitherSyntax { implicit final def catsSyntaxEither[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab) @@ -373,11 +374,3 @@ final class EitherIdOps[A](val obj: A) extends AnyVal { def rightNel[B]: Either[NonEmptyList[B], A] = Right(obj) } - -/** Convenience methods to use `Either` syntax inside `Either` syntax definitions. */ -private[cats] object EitherUtil { - def leftCast[A, B, C](right: Right[A, B]): Either[C, B] = - right.asInstanceOf[Either[C, B]] - def rightCast[A, B, C](left: Left[A, B]): Either[A, C] = - left.asInstanceOf[Either[A, C]] -} diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index 6912ffd88a..3a7f7897e5 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -22,6 +22,12 @@ trait MonadErrorLaws[F[_], E] extends ApplicativeErrorLaws[F, E] with MonadLaws[ def rethrowAttempt[A](fa: F[A]): IsEq[F[A]] = F.rethrow(F.attempt(fa)) <-> fa + + def redeemDerivedFromAttemptMap[A, B](fa: F[A], fe: E => B, fs: A => B): IsEq[F[B]] = + F.redeem(fa)(fe, fs) <-> F.map(F.attempt(fa))(_.fold(fe, fs)) + + def redeemWithDerivedFromAttemptFlatMap[A, B](fa: F[A], fe: E => F[B], fs: A => F[B]): IsEq[F[B]] = + F.redeemWith(fa)(fe, fs) <-> F.flatMap(F.attempt(fa))(_.fold(fe, fs)) } object MonadErrorLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 53e23ca65d..62688c72df 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -43,7 +43,9 @@ trait MonadErrorTests[F[_], E] extends ApplicativeErrorTests[F, E] with MonadTes "monadError ensureOr consistency" -> forAll(laws.monadErrorEnsureOrConsistency[A] _), "monadError adaptError pure" -> forAll(laws.adaptErrorPure[A] _), "monadError adaptError raise" -> forAll(laws.adaptErrorRaise[A] _), - "monadError rethrow attempt" -> forAll(laws.rethrowAttempt[A] _) + "monadError rethrow attempt" -> forAll(laws.rethrowAttempt[A] _), + "monadError redeem is derived from attempt and map" -> forAll(laws.redeemDerivedFromAttemptMap[A, B] _), + "monadError redeemWith is derived from attempt and flatMap" -> forAll(laws.redeemWithDerivedFromAttemptFlatMap[A, B] _) ) } } diff --git a/scalastyle-config.xml b/scalastyle-config.xml index 081d009534..baad869d2e 100644 --- a/scalastyle-config.xml +++ b/scalastyle-config.xml @@ -1,11 +1,6 @@ Scalastyle standard configuration - - - - - diff --git a/tests/src/test/scala/cats/tests/RegressionSuite.scala b/tests/src/test/scala/cats/tests/RegressionSuite.scala index b5b9f28803..c4a5727e6c 100644 --- a/tests/src/test/scala/cats/tests/RegressionSuite.scala +++ b/tests/src/test/scala/cats/tests/RegressionSuite.scala @@ -131,19 +131,4 @@ class RegressionSuite extends CatsSuite { NonEmptyList.of(6,7,8).traverse_(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(1) } - - test("#2022 EitherT syntax no long works the old way") { - import data._ - - - EitherT.right[String](Option(1)).handleErrorWith((_: String) => EitherT.pure(2)) - - { - implicit val me = MonadError[EitherT[Option, String, ?], Unit] - EitherT.right[String](Option(1)).handleErrorWith((_: Unit) => EitherT.pure(2)) - } - - - } - } From 2f42f4e1bec15c8c8e223ec14830f4bbf4c7a4f9 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Wed, 13 Nov 2019 10:10:27 -0600 Subject: [PATCH 2/6] Revert non-redeem parts of 3457276881fd748143712e097df68d138feb60df --- build.sbt | 7 - .../cats/internals/FutureShims.scala | 20 - .../cats/internals/FutureShims.scala | 30 - .../main/scala/cats/ApplicativeError.scala | 6 +- core/src/main/scala/cats/Parallel.scala | 34 +- core/src/main/scala/cats/data/EitherT.scala | 1056 +---------------- core/src/main/scala/cats/data/Ior.scala | 4 +- .../main/scala/cats/instances/either.scala | 46 +- .../main/scala/cats/instances/future.scala | 14 +- .../scala/cats/internals/EitherUtil.scala | 35 - core/src/main/scala/cats/syntax/either.scala | 9 +- scalastyle-config.xml | 5 + .../scala/cats/tests/RegressionSuite.scala | 15 + 13 files changed, 134 insertions(+), 1147 deletions(-) delete mode 100644 core/src/main/scala-2.11-/cats/internals/FutureShims.scala delete mode 100644 core/src/main/scala-2.12+/cats/internals/FutureShims.scala delete mode 100644 core/src/main/scala/cats/internals/EitherUtil.scala diff --git a/build.sbt b/build.sbt index d6ccb5f97e..7065a20f22 100644 --- a/build.sbt +++ b/build.sbt @@ -49,13 +49,6 @@ lazy val commonSettings = Seq( scalacOptions in (Compile, doc) := (scalacOptions in (Compile, doc)).value.filter(_ != "-Xfatal-warnings"), ivyConfigurations += CompileTime, unmanagedClasspath in Compile ++= update.value.select(configurationFilter(CompileTime.name)), - unmanagedSourceDirectories in Compile ++= { - val bd = baseDirectory.value - if (CrossVersion.partialVersion(scalaVersion.value) exists (_._2 >= 12)) - CrossType.Pure.sharedSrcDir(bd, "main").toList map (f => file(f.getPath + "-2.12+")) - else - CrossType.Pure.sharedSrcDir(bd, "main").toList map (f => file(f.getPath + "-2.11-")) - }, unmanagedSourceDirectories in Test ++= { val bd = baseDirectory.value if (CrossVersion.partialVersion(scalaVersion.value) exists (_._2 >= 11)) diff --git a/core/src/main/scala-2.11-/cats/internals/FutureShims.scala b/core/src/main/scala-2.11-/cats/internals/FutureShims.scala deleted file mode 100644 index 16969e8c82..0000000000 --- a/core/src/main/scala-2.11-/cats/internals/FutureShims.scala +++ /dev/null @@ -1,20 +0,0 @@ -package cats.internals - -import scala.concurrent.{ExecutionContext, Future} - -private[cats] object FutureShims { - /** - * Exposes the new `Future#transformWith` from Scala 2.12 in a way - * that's compatible with Scala 2.11. - */ - def redeemWith[A, B](fa: Future[A])(fe: Throwable => Future[B], fs: A => Future[B]) - (implicit ec: ExecutionContext): Future[B] = - attempt(fa).flatMap(_.fold(fe, fs)) - - /** - * Exposes an optimized `attempt` for `Future` whose implementation - * depends on the Scala version. - */ - def attempt[A](fa: Future[A])(implicit ec: ExecutionContext): Future[Either[Throwable, A]] = - fa.map(Right[Throwable, A]).recover { case e => Left(e) } -} diff --git a/core/src/main/scala-2.12+/cats/internals/FutureShims.scala b/core/src/main/scala-2.12+/cats/internals/FutureShims.scala deleted file mode 100644 index 6d2393588b..0000000000 --- a/core/src/main/scala-2.12+/cats/internals/FutureShims.scala +++ /dev/null @@ -1,30 +0,0 @@ -package cats.internals - -import scala.concurrent.{ExecutionContext, Future} -import scala.util.{Failure, Success} - -private[cats] object FutureShims { - /** - * Exposes the new `Future#transformWith` from Scala 2.12 in a way - * that's compatible with Scala 2.11. - */ - def redeemWith[A, B](fa: Future[A])(fe: Throwable => Future[B], fs: A => Future[B]) - (implicit ec: ExecutionContext): Future[B] = { - - fa.transformWith { - case Success(a) => fs(a) - case Failure(e) => fe(e) - } - } - - /** - * Exposes an optimized `attempt` for `Future` whose implementation - * depends on the Scala version. - */ - def attempt[A](fa: Future[A])(implicit ec: ExecutionContext): Future[Either[Throwable, A]] = - fa.transformWith(r => Future.successful( - r match { - case Success(a) => Right(a) - case Failure(e) => Left(e) - })) -} diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index 68508b48c7..b450ee30f9 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -3,7 +3,6 @@ package cats import cats.data.EitherT import scala.util.{ Failure, Success, Try } import scala.util.control.NonFatal -import cats.internals.EitherUtil.rightBox /** * An applicative that also allows you to raise and or handle an error value. @@ -63,8 +62,9 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { * * All non-fatal errors should be handled by this method. */ - def attempt[A](fa: F[A]): F[Either[E, A]] = - handleErrorWith(map(fa)(rightBox[E, A]))(e => pure(Left[E, A](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.EitherT]] for diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index d31537bf81..3d48d4fe03 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -78,32 +78,30 @@ trait Parallel[M[_], F[_]] extends NonEmptyParallel[M, F] { */ def applicativeError[E](implicit E: MonadError[M, E]): ApplicativeError[F, E] = new ApplicativeError[F, E] { - override def raiseError[A](e: E): F[A] = + def raiseError[A](e: E): F[A] = parallel(MonadError[M, E].raiseError(e)) - override def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { + def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { val ma = MonadError[M, E].handleErrorWith(sequential(fa))(f andThen sequential.apply) parallel(ma) } - override def pure[A](x: A): F[A] = - applicative.pure(x) - override def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = - applicative.ap(ff)(fa) - override def attempt[A](fa: F[A]): F[Either[E, A]] = - parallel(MonadError[M, E].attempt(sequential(fa))) - override def map[A, B](fa: F[A])(f: (A) => B): F[B] = - applicative.map(fa)(f) - override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = - applicative.product(fa, fb) - override def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = - applicative.map2(fa, fb)(f) + def pure[A](x: A): F[A] = applicative.pure(x) + + def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = applicative.ap(ff)(fa) + + override def map[A, B](fa: F[A])(f: (A) => B): F[B] = applicative.map(fa)(f) + + override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = applicative.product(fa, fb) + + override def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = applicative.map2(fa, fb)(f) + override def map2Eval[A, B, Z](fa: F[A], fb: Eval[F[B]])(f: (A, B) => Z): Eval[F[Z]] = applicative.map2Eval(fa, fb)(f) - override def unlessA[A](cond: Boolean)(f: => F[A]): F[Unit] = - applicative.unlessA(cond)(f) - override def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = - applicative.whenA(cond)(f) + + override def unlessA[A](cond: Boolean)(f: => F[A]): F[Unit] = applicative.unlessA(cond)(f) + + override def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = applicative.whenA(cond)(f) } } diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 470607719f..516034d7d0 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -1,1058 +1,123 @@ package cats package data +import cats.Bifunctor import cats.instances.either._ import cats.syntax.either._ -import cats.internals.EitherUtil.{eitherCast, rightBox, leftBox} /** - * Transformer for `Either`, allowing the effect of an arbitrary type - * constructor `F` to be combined with the fail-fast effect of `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]]`. - * - * For example `EitherT` can be combined with types such as [[Eval]], - * `scala.concurrent.Future` or `cats.effect.IO` for principled error - * handling: - * - * {{{ - * import cats.Eval - * import cats.Eval.always - * import cats.data.EitherT - * import java.lang.Long.{parseNum => javaParseNum} - * - * def parseNum(s: String, radix: Int = 10): EitherT[Eval, NumberFormatException, Long] = - * EitherT(always { - * try - * Right(javaParseNum(s, radix)) - * catch { case e: NumberFormatException => - * Left(e) - * } - * }) - * }}} - * - * Note that in this function the error type is part of the signature and - * is more specific than the customary `Throwable` or `Exception`. You can - * always "upcast" its error type to `Throwable` later using [[upcastL]]: - * - * {{{ - * val num: EitherT[Eval, Throwable, Long] = - * parseNum("10210", 10).upcastL - * }}} - * - * The power of `EitherT` is that it combines `F[_]` with the `Either` - * data type, with the result still being a `MonadError`, so you can - * comfortably use operations such as [[map]], [[flatMap]], [[attempt]] - * and others: - * - * {{{ - * def parseEvenNum(s: String, radix: Int = 10): EitherT[Eval, NumberFormatException, Long] = - * parseNum(s, radix).flatMap { i => - * if (i % 2 == 0) - * EitherT.rightT(i) - * else - * EitherT.leftT(new NumberFormatException(s"Not an even number: $$i")) - * } - * }}} - * - * Tip: An `F[A]` can be lifted in to `EitherT[F, E, A]` via [[EitherT.right]] - * and lifted in to a `EitherT[F, A, B]` via [[EitherT.left]]. - * - * {{{ - * def rand(seed: Long): Eval[Int] = - * Eval.always { - * val newSeed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL - * (newSeed >>> 16).toInt - * } - * - * // Lifting values - * def randPredicate(seed: Long)(p: Int => Boolean): EitherT[Eval, String, Int] = - * EitherT.right(rand(seed)).flatMap { nr => - * if (p(nr)) - * EitherT.leftT("Predicate was false") - * else - * EitherT.pure(nr) - * } - * }}} - * - * @define monadErrorF For example data types like `cats.effect.IO` implement - * [[MonadError]] and can thus express computations that can fail on their - * own, usually via `Throwable`. When working with `EitherT`, if the - * underlying `F` is implementing [[ApplicativeError]], sometimes it is - * necessary to recover from such errors thrown in `F[_]`. + * `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]]) { - /** - * Folds the underlying `Either` value, yielding an `F[_]` as result. - * - * Example: - * {{{ - * val parsed: Eval[Long] = parseNum(num).fold(_ => 0, x => x) - * }}} - */ - def fold[C](fa: A => C, fb: B => C)(implicit F: Functor[F]): F[C] = - F.map(value)(_.fold(fa, fb)) + def fold[C](fa: A => C, fb: B => C)(implicit F: Functor[F]): F[C] = F.map(value)(_.fold(fa, fb)) - /** - * Queries the underlying `Either` value, returning `true` if it's a `Left`, - * or `false` otherwise. - * - * {{{ - * // Yields `false` on evaluation - * EitherT.rightT[Eval, Throwable](10).isLeft - * - * // Yields `true` on evaluation - * EitherT.leftT[Eval, Int]("My error").isLeft - * }}} - * - * @see [[isRight]] - */ - def isLeft(implicit F: Functor[F]): F[Boolean] = - F.map(value)(_.isLeft) + def isLeft(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.isLeft) - /** - * Queries the underlying `Either` value, returning `true` if it's a `Right`, - * or `false` otherwise. - * - * {{{ - * // Yields `true` on evaluation - * EitherT.rightT[Eval, String](10).isRight - * - * // Yields `false` on evaluation - * EitherT.leftT[Eval, Int]("My error").isRight - * }}} - * - * @see [[isLeft]] - */ - def isRight(implicit F: Functor[F]): F[Boolean] = - F.map(value)(_.isRight) + def isRight(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.isRight) - /** - * Swaps the left and right values on evaluation, such that if the - * underlying `Either` is a `Left`, then returns a `Right` on the evaluation - * of `F[_]` or vice versa. - * - * {{{ - * val rt = EitherT.rightT[Eval, String](10) - * - * rt.value.value - * //=> res: Either[String,Int] = Right(10) - * - * rt.swap.value.value - * //=> res: Either[Int,String] = Left(10) - * }}} - */ - def swap(implicit F: Functor[F]): EitherT[F, B, A] = - EitherT(F.map(value)(_.swap)) + def swap(implicit F: Functor[F]): EitherT[F, B, A] = EitherT(F.map(value)(_.swap)) - /** - * Casts the left type parameter to a super type. - * - * Like many types described in Cats, the type parameters of `EitherT` - * are invariant, even if the `Either` data type is covariant in both - * type parameters. - * - * This operator is a shortcut for safely upcasting the left type parameter - * to a compatible super type, an operation that would be automatic under - * Scala's type system if the parameters were declared as being covariant. - * - * Example: - * - * {{{ - * val num: EitherT[Eval, NumberFormatException, Long] = - * EitherT.rightT(10210) - * - * val num2: EitherT[Eval, Throwable, Long] = - * num.upcastL - * }}} - * - * @see [[upcastR]] - */ - @inline def upcastL[AA >: A]: EitherT[F, AA, B] = - this.asInstanceOf[EitherT[F, AA, B]] + def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = F.map(value)(_.getOrElse(default)) - /** - * Casts the right type parameter to a super type. - * - * Like many types described in Cats, the type parameters of `EitherT` - * are invariant, even if the `Either` data type is covariant in both - * type parameters. - * - * This operator is a shortcut for safely upcasting the right type parameter - * to a compatible super type, an operation that would be automatic under - * Scala's type system if the parameters were declared as being covariant. - * - * {{{ - * val list: EitherT[Eval, String, List[Int]] = - * EitherT.rightT(List(10)) - * - * val iter: EitherT[Eval, String, Iterable[Int]] = - * list.upcastR - * }}} - * - * @see [[upcastL]] - */ - @inline def upcastR[BB >: B]: EitherT[F, A, BB] = - this.asInstanceOf[EitherT[F, A, BB]] - - /** - * If the underlying `Either` is a `Right`, then returns its value in - * the `F[_]` context, otherwise returns `default`. - * - * {{{ - * val num1: EitherT[Eval, String, Long] = - * EitherT.rightT(10) - * - * // Yields 10 - * num1.getOrElse(0L) - * - * val num2: EitherT[Eval, String, Long] = - * EitherT.leftT("Invalid number") - * - * // Yields 0 - * num2.getOrElse(0L) - * }}} - * - * @see [[getOrElseF]] and [[orElse]] - * - * @param default is a thunk to evaluate in case the underlying - * `Either` produced by `F[_]` is a `Left` value - */ - def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = - F.map(value)(_.getOrElse(default)) - - /** - * If the underlying `Either` is a `Right`, then returns its value in - * the `F[_]` context, otherwise evaluate `default` instead. - * - * {{{ - * val num1: EitherT[Eval, String, Long] = - * EitherT.rightT(10) - * - * // Yields 10 - * num1.getOrElseF(Eval.now(0L)) - * - * val num2: EitherT[Eval, String, Long] = - * EitherT.leftT("Invalid number") - * - * // Yields Long.MaxValue - * num2.getOrElseF(Eval.always(Long.MinValue - 1)) - * }}} - * - * @see [[getOrElse]] and [[orElse]] - * - * @param default is a thunk to evaluate in case the underlying - * `Either` produced by `F[_]` is a `Left` value - */ - def getOrElseF[BB >: B](default: => F[BB])(implicit F: Monad[F]): F[BB] = + def getOrElseF[BB >: B](default: => F[BB])(implicit F: Monad[F]): F[BB] = { F.flatMap(value) { + case Left(_) => default case Right(b) => F.pure(b) - // N.B. pattern match does not use `case Left(_)` on purpose - case _ => default } + } - /** - * Returns the result of the source unchanged if the underlying `Either` - * is evaluated to a `Right` valued, otherwise evaluates the given - * `default` thunk. - * - * {{{ - * def randEven(seed: Long): EitherT[Eval, String, Int] = - * randPredicate(seed)(_ % 2 == 0) orElse randEven(seed + 1) - * }}} - */ - def orElse[AA, BB >: B](default: => EitherT[F, AA, BB])(implicit F: Monad[F]): EitherT[F, AA, BB] = - EitherT(F.flatMap(value) { - case r @ Right(_) => F.pure(eitherCast(r)) - // N.B. pattern match does not use `case Left(_)` on purpose - case _ => default.value - }) - - /** - * Handles errors by materializing them into `Either` values. - * - * This is the implementation of [[ApplicativeError.attempt]]. - * - * Example: - * {{{ - * parseNum(x).attempt.map { - * case Right(num) => num - * case Left(_) => 0L - * } - * }}} - * - * @see [[attemptF]] for recovering errors thrown in `F[_]` - * - * @see [[redeem]] and [[redeemWith]] for optimizations on `attempt.map` - * and `attempt.flatMap` respectively - */ - def attempt(implicit F: Functor[F]): EitherT[F, A, Either[A, B]] = - EitherT.right(value) - - /** - * Handles errors thrown in `F[_]` (if it implements [[ApplicativeError]]), - * by materializing them into `Either` values. - * - * $monadErrorF - * - * Example: - * {{{ - * import cats.effect.IO - * - * val rio = EitherT.pure[IO, String](10) - * - * // Yields Right(10) - * rio.attemptF - * - * val dummy = new RuntimeException("dummy") - * val lio = EitherT[IO, String, Int](IO.raiseError(dummy)) - * - * // Yields Left(RuntimeException("dummy")) - * lio.attemptF - * }}} - * - * Note that in this sample we are materializing the `Throwable` of the - * underlying `IO`, even the source `EitherT` value is not parametrized - * with it. - * - * @see [[attempt]] for recovering errors expressed via `EitherT` - * - * @see [[redeemF]] and [[redeemWithF]] for optimizations on `attemptF.map` - * and `attemptF.flatMap` respectively - */ - def attemptF[E](implicit F: ApplicativeError[F, E]): EitherT[F, A, Either[E, B]] = - EitherT(F.map(F.attempt(value)) { - case error @ Left(_) => - Right(eitherCast(error)) - case Right(eitherB) => - eitherB match { - case right @ Right(_) => Right(eitherCast(right)) - // N.B. pattern match does not do `case Left(_)` on purpose - case _ => eitherCast(eitherB) - } - }) - - /** - * Handle any error, potentially recovering from it, by mapping it via - * the provided function. - * - * This is the implementation of [[ApplicativeError.handleError]]. - * - * Example: - * {{{ - * parseNum(s).handleError(_ => 0L) - * }}} - * - * @see [[handleErrorWith]] for recovering errors by mapping them to - * `EitherT` values (aka the equivalent of `flatMap` for errors) - * - * @see [[handleErrorF]] for recovering errors thrown in `F[_]` - */ - def handleError(f: A => B)(implicit F: Functor[F]): EitherT[F, A, B] = - EitherT(F.map(value) { - case Left(a) => Right(f(a)) - // N.B. pattern match does not do `case Right(_)` on purpose - case right => right - }) - - /** - * Handles any error in `F[_]`, potentially recovering from it, by mapping - * it via the provided function. - * - * $monadErrorF - * - * Example: - * {{{ - * import cats.effect.IO - * import java.lang.Long.{parseNum => javaParseNum} - * - * def parseNum(s: String, r: Int = 10): IO[Long] = - * IO(javaParseNum(s, r)) - * - * val eio = EitherT.right[String](parseNum("invalid")) - * - * // Yields 0L on evaluation - * eio.handleErrorF(_ => 0L) - * }}} - * - * @see [[handleErrorWithF]] for recovering errors by mapping them to - * `EitherT` values (aka the equivalent of `flatMap` for errors) - * - * @see [[handleError]] for recovering errors expressed via `EitherT` - */ - def handleErrorF[E](f: E => B)(implicit F: ApplicativeError[F, E]): EitherT[F, A, B] = - EitherT(F.handleError(value)(e => Right(f(e)))) - - /** - * Handle any error, potentially recovering from it, by mapping it via - * the provided function to another `EitherT` value. - * - * This is the implementation of [[ApplicativeError.handleErrorWith]]. - * - * Example: - * {{{ - * import cats.Eval - * import java.lang.Long.{parseNum => javaParseNum} - * - * def parseNum(s: String, r: Int = 10): EitherT[Eval, String, Long] = - * EitherT(Eval.always { - * try - * Right(javaParseNum(s, r)) - * catch { case _: NumberFormatException => - * Left("invalid number") - * } - * }) - * - * // Yields 10210 because there's no error here - * parseNum("10210").handleErrorWith(_ => EitherT.pure(0L)) - * - * parseNum("Hello").handleErrorWith { - * case "invalid number" => - * EitherT.pure(0L) - * case other => - * // Rethrowing error, because we don't know what it is - * EitherT.leftT(other) - * } - * }}} - * - * @see [[handleError]] for recovering errors by mapping them to simple values - * - * @see [[handleErrorWithF]] for recovering errors thrown in `F[_]` - */ - def handleErrorWith(f: A => EitherT[F, A, B])(implicit F: Monad[F]): EitherT[F, A, B] = + def orElse[AA, BB >: B](default: => EitherT[F, AA, BB])(implicit F: Monad[F]): EitherT[F, AA, BB] = { EitherT(F.flatMap(value) { - case Left(a) => f(a).value - // N.B. pattern match does not do `case Right(_)` on purpose - case right => F.pure(right) + case Left(_) => default.value + case r @ Right(_) => F.pure(r.leftCast) }) + } - /** - * Handles any error in `F[_]`, potentially recovering from it, by mapping - * it via the provided function to another `EitherT` value. - * - * $monadErrorF - * - * Example: - * {{{ - * import cats.effect.IO - * import java.lang.Long.{parseNum => javaParseNum} - * - * def parseNum(s: String, r: Int = 10): IO[Long] = - * IO(javaParseNum(s, r)) - * - * val eio = EitherT.right[String](parseNum("invalid")) - * - * // Yields 0L on evaluation - * eio.handleErrorWithF { - * case _: NumberFormatException => - * EitherT.pure(0L) - * case other => - * // We are only recovering from NumberFormatException here because we - * // don't know of other exceptions that could be thrown and thus we - * // prefer to treat them as unrecoverable - * EitherT.right(IO.raiseError(other)) - * } - * }}} - * - * @see [[handleErrorF]] for recovering errors by mapping them to simple values - * - * @see [[handleErrorWith]] for recovering errors expressed via `EitherT` - */ - def handleErrorWithF[E](f: E => EitherT[F, A, B])(implicit F: ApplicativeError[F, E]): EitherT[F, A, B] = - EitherT(F.handleErrorWith(value)(f(_).value)) - - /** - * Handle any error, potentially recovering from it, by mapping it via - * the provided partial function. In case the provided partial function - * isn't defined for the given input, then the error is rethrown. - * - * This is the implementation of [[ApplicativeError.recover]]. - * - * Example: - * {{{ - * parseNum(s).recover { - * case "invalid number" => 0L - * } - * }}} - * - * @see [[handleError]] for handling all errors via a total function - * - * @see [[recoverF]] for handling errors thrown in `F[_]` - */ def recover(pf: PartialFunction[A, B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(value)(_.recover(pf))) - /** - * Handles any error in `F[_]`, potentially recovering from it, by mapping - * it via the provided partial function, with unhandled errors being - * re-thrown in the `F[_]` context. - * - * $monadErrorF - * - * Example: - * {{{ - * import cats.effect.IO - * import java.lang.Long.{parseNum => javaParseNum} - * - * def parseNum(s: String, r: Int = 10): IO[Long] = - * IO(javaParseNum(s, r)) - * - * val eio = EitherT.right[String](parseNum("invalid")) - * - * // Yields 0L on evaluation - * eio.recoverF { - * case _: NumberFormatException => 0L - * } - * }}} - * - * @see [[handleErrorF]] for recovering all errors via a total function - * - * @see [[recover]] for recovering errors expressed via `EitherT` - */ - def recoverF[E](pf: PartialFunction[E, B])(implicit F: ApplicativeError[F, E]): EitherT[F, A, B] = - EitherT(F.recover(value)(pf.andThen(rightBox))) - - /** - * Handle any error, potentially recovering from it, by mapping it via - * the provided partial function to another `EitherT` value, with - * unhandled errors being re-thrown in the `EitherT` context. - * - * This is the implementation of [[ApplicativeError.recoverWith]]. - * - * Example: - * {{{ - * import cats.Eval - * import java.lang.Long.{parseNum => javaParseNum} - * - * def parseNum(s: String, r: Int = 10): EitherT[Eval, String, Long] = - * EitherT(Eval.always { - * try - * Right(javaParseNum(s, r)) - * catch { case _: NumberFormatException => - * Left("invalid number") - * } - * }) - * - * parseNum("Hello").recoverWith { - * case "invalid number" => - * EitherT.pure(0L) - * } - * }}} - * - * @see [[handleErrorWith]] for recovering all errors via a total function - * - * @see [[recoverWithF]] for recovering errors thrown in `F[_]` - */ 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) }) - /** - * Handles errors in `F[_]`, potentially recovering from them, by mapping - * errors via the provided partial function to other `EitherT` values, - * unhandled errors being rethrown in the `F[_]` context. - * - * $monadErrorF - * - * Example: - * {{{ - * import cats.effect.IO - * import java.lang.Long.{parseNum => javaParseNum} - * - * def parseNum(s: String, r: Int = 10): IO[Long] = - * IO(javaParseNum(s, r)) - * - * val eio = EitherT.right[String](parseNum("invalid")) - * - * // Yields 0L on evaluation - * eio.recoverWithF { - * case _: NumberFormatException => - * EitherT.pure(0L) - * } - * }}} - * - * @see [[handleErrorWithF]] for recovering errors via a total function - * - * @see [[recoverWith]] for recovering errors expressed via `EitherT` - */ - def recoverWithF[E](pf: PartialFunction[E, EitherT[F, A, B]])(implicit F: ApplicativeError[F, E]): EitherT[F, A, B] = - EitherT(F.recoverWith(value)(pf.andThen(_.value))) + def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = fold(f, identity) - /** - * Given a pair of functions, transforms the underlying `Either` value - * to a (successful) `Right` result. - * - * This is the implementation of [[MonadError.redeem]] and is an operation - * that can be derived from `attempt.map`, being an optimization on it: - * - * {{{ - * et.redeem(recover, map) <-> et.attempt.map(_.fold(recover, map)) - * }}} - * - * Example: - * {{{ - * parseNum(x).redeem( - * // error recovery - * _ => 0L, - * num => num - * ) - * }}} - * - * @see [[attempt]] for materializing errors - * - * @see [[redeemF]] for redeeming errors thrown in the `F[_]` context - */ - def redeem[R](recover: A => R, map: B => R)(implicit F: Functor[F]): EitherT[F, A, R] = - EitherT(F.map(value) { - case Right(b) => Right(map(b)) - case Left(a) => Right(recover(a)) - }) - - /** - * Returns a new `EitherT` value that transforms the result of the source, - * given the `recover` or `map` functions, which get executed depending - * on whether the underlying `F[_]` is successful or if it ends in error. - * - * $monadErrorF - * - * Example: - * {{{ - * import cats.effect.IO - * - * val rio = EitherT.pure[IO, String](10) - * - * val dummy = new RuntimeException("dummy") - * val lio = EitherT[IO, String, Int](IO.raiseError(dummy)) - * - * // Yields 0L on evaluation - * lio.redeemF( - * error => 0L, - * num => num) - * }}} - * - * Note that in this sample we are recovering from the `Throwable` of the - * underlying `IO`, even the source `EitherT` value is not parametrized - * with it. - * - * This function is an optimization on usage of [[attemptF]] and `map`, - * as this equivalence always holds: - * - * {{{ - * et.redeemF(fe, fb) <-> et.attemptF.map(_.fold(fe, fb)) - * }}} - * - * @see [[attemptF]] for materialized errors thrown in the `F[_]` context - * - * @see [[redeem]] for recovering errors expressed via `EitherT` - */ - def redeemF[E, R](recover: E => R, map: B => R)(implicit F: MonadError[F, E]): EitherT[F, A, R] = - EitherT(F.redeem(value)(e => Right(recover(e)), { - case Right(a) => Right(map(a)) - case error => eitherCast(error) - })) - - /** - * Returns a new value that transforms the result of the source, - * given the `recover` or `bind` functions, which get executed depending - * on whether the underlying `Either` value is a `Left` or a `Right`. - * - * This is the implementation of [[MonadError.redeemWith]] and is an operation - * that can be derived from `attempt.flatMap`, being an optimization on it: - * - * {{{ - * et.redeemWith(recover, map) <-> et.attempt.flatMap(_.fold(recover, map)) - * }}} - * - * Example: - * {{{ - * parseNum(x).redeemWith( - * // error recovery - * error => error match { - * case "invalid number" => EitherT.pure(0L) - * case other => - * // Rethrowing unknown error types - * EitherT.leftT(other) - * }, - * num => - * // Binding to another value; we could do something more - * // fancy here, like an actual bind continuation - * EitherT.pure(num) - * ) - * }}} - * - * @see [[attempt]] for materializing errors - * - * @see [[redeemWithF]] for redeeming errors thrown in the `F[_]` context - */ - def redeemWith[R](recover: A => EitherT[F, A, R], bind: B => EitherT[F, A, R])(implicit F: Monad[F]): EitherT[F, A, R] = - EitherT(F.flatMap(value) { - case Right(b) => bind(b).value - case Left(a) => recover(a).value - }) - - /** - * Returns a new `EitherT` value that transforms the result of the source, - * given the `recover` or `bind` functions, which get executed depending - * on whether the underlying `F[_]` is successful or if it ends in error. - * - * $monadErrorF - * - * Example: - * {{{ - * import cats.effect.IO - * - * val rio = EitherT.pure[IO, String](10) - * - * val dummy = new RuntimeException("dummy") - * val lio = EitherT[IO, String, Int](IO.raiseError(dummy)) - * - * // Yields 0L on evaluation - * lio.redeemWithF( - * // error recovery - * error => error match { - * case `dummy` => EitherT.pure(0L) - * case other => - * // Rethrowing other errors we don't recognize - * EitherT(IO.raiseError(other)) - * }, - * num => { - * // Binding to another value; we could do something more - * // fancy here, like an actual bind continuation - * EitherT.pure(num) - * }) - * }}} - * - * Note that in this sample we are recovering from the `Throwable` of the - * underlying `IO`, even the source `EitherT` value is not parametrized - * with it. - * - * This function is an optimization on usage of [[attemptF]] and `flatMap`, - * as this equivalence always holds: - * - * {{{ - * et.redeemWithF(fe, fb) <-> et.attemptF.flatMap(_.fold(fe, fb)) - * }}} - * - * @see [[attemptF]] for materialized errors thrown in the `F[_]` context - * - * @see [[redeemWith]] for recovering errors expressed via `EitherT` - */ - def redeemWithF[E, R](recover: E => EitherT[F, A, R], bind: B => EitherT[F, A, R])(implicit F: MonadError[F, E]): EitherT[F, A, R] = - EitherT(F.redeemWith(value)(e => recover(e).value, { - case Right(a) => bind(a).value - case error => F.pure(eitherCast(error)) - })) - - /** - * Returns the `Right` value in `F[_]`, or transforms the `Left` value - * via the provided function. - * - * {{{ - * val et1 = EitherT.right[String](Eval.always(10)) - * // Yields 10 - * et1.valueOr(_ => 0L) - * - * val et2 = EitherT.left[int](Eval.always("error")) - * // Yields 0 - * et2.valueOr(_ => 0L) - * }}} - */ - def valueOr[BB >: B](f: A => BB)(implicit F: Functor[F]): F[BB] = - fold(f, identity) - - /** - * Returns the `Right` value in `F[_]`, or transforms the `Left` value - * via the provided function that processes the final result in `F[_]`. - * - * {{{ - * val et1 = EitherT.right[String](Eval.always(10)) - * // Yields 10 - * et1.valueOrF(_ => parseNum("0")) - * - * val et2 = EitherT.left[int](Eval.always("error")) - * // Yields 0 - * et2.valueOrF(_ => parseNum("0")) - * }}} - */ - def valueOrF[BB >: B](f: A => F[BB])(implicit F: Monad[F]): F[BB] = - F.flatMap(value){ - case Left(a) => f(a) - case Right(b) => F.pure(b) - } + def valueOrF[BB >: B](f: A => F[BB])(implicit F: Monad[F]): F[BB] = { + F.flatMap(value){ + case Left(a) => f(a) + case Right(b) => F.pure(b) + } + } - /** - * Returns `true` if `Left` or returns the result of the application of - * the given predicate function to the `Right` value. - */ - def forall(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = - F.map(value)(_.forall(f)) + def forall(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.forall(f)) - /** - * Returns `false` if `Left` or returns the result of the given predicate - * function applied to the `Right` value. - */ - def exists(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = - F.map(value)(_.exists(f)) + def exists(f: B => Boolean)(implicit F: Functor[F]): F[Boolean] = F.map(value)(_.exists(f)) - /** - * Turns a successful value into an error if it does not satisfy the given - * predicate. - * - * This is the implementation of [[MonadError.ensure]]. - */ def ensure[AA >: A](onFailure: => AA)(f: B => Boolean)(implicit F: Functor[F]): EitherT[F, AA, B] = EitherT(F.map(value)(_.ensure(onFailure)(f))) - /** - * Turns a successful value into an error specified by the `onFailure` - * function if it does not satisfy a given predicate. - * - * This is the implementation of [[MonadError.ensureOr]]. - */ def ensureOr[AA >: A](onFailure: B => AA)(f: B => Boolean)(implicit F: Functor[F]): EitherT[F, AA, B] = EitherT(F.map(value)(_.ensureOr(onFailure)(f))) - /** - * Converts this `EitherT` to [[OptionT]], `Right` values being converted - * to `Some` and `Left` values being converted to `None`. - */ - def toOption(implicit F: Functor[F]): OptionT[F, B] = - OptionT(F.map(value)(_.toOption)) + def toOption(implicit F: Functor[F]): OptionT[F, B] = OptionT(F.map(value)(_.toOption)) - /** - * Given a `G[_]` type parameter that implements [[Alternative]], converts - * this `EitherT` value to it, keeping the `F[_]` context. - * - * `Alternative` is basically [[MonoidK]] with [[Applicative]], meaning - * that `Left` values get translated to [[MonoidK.empty]] and `Right` values - * get translated to [[Applicative.pure]]. - * - * For example: - * - * {{{ - * val num = EitherT.right[String](Eval.always(1)) - * - * val numOpt: Eval[Option[Int]] = num.to[Option] - * numOpt.value - * //=> Some(1) - * - * val numList: Eval[List[Int]] = num.to[List] - * numList.value - * //=> List(1) - * - * val err = EitherT.left[Int](Eval.always("error")) - * - * val errOpt: Eval[Option[Int]] = err.to[Option] - * errOpt.value - * //=> None - * - * val errList: Eval[List[Int]] = err.to[List] - * errList.value - * //=> List() - * }}} - */ def to[G[_]](implicit F: Functor[F], G: Alternative[G]): F[G[B]] = F.map(value)(_.to[G]) - /** - * Given ab `F[_]` that implements [[Alternative]], collect all - * `Right` values or convert `Left` values to [[MonoidK.empty empty]]. - * - * Example: - * {{{ - * def validateNum(num: Int): Either[String, Int] = - * if (num % 2 == 0) Right(num) - * else Left("number is odd") - * - * val et: EitherT[List, String, Int] = - * EitherT((0 until 1000).toList.map(validateNum)) - * - * // Yields 0, 2, 4 ... 98 - * val evens: List[Int] = et.collectRight - * }}} - */ def collectRight(implicit FA: Alternative[F], FM: Monad[F]): F[B] = FM.flatMap(value)(_.to[F]) - /** - * The `bimap` operation can apply a transformation to each "side" of - * the underlying `Either` value. - * - * This is the [[Bifunctor.bimap]] implementation. It's very much like - * normal [[map]], except that it can also transform the left side as well. - * - * Example: - * {{{ - * val et1: EitherT[Eval, String, Int] = - * EitherT.leftT("12012") - * - * // Yields Left(12012 : Int) - * val et2: EitherT[Eval, Int, String] = - * et1.bimap( - * left => left.toInt, - * right => right.toString) - * - * // Yields Left(23123) - * val et3: EitherT[Eval, Int, Int] = - * et2.bimap(a => a + 11111, _.toInt) - * }}} - * - * @param fa is the mapping function applied to `Left` values - * @param fb is the mapping function applied to `Right` values - */ - 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 bimap[C, D](fa: A => C, fb: B => D)(implicit F: Functor[F]): EitherT[F, C, D] = EitherT(F.map(value)(_.bimap(fa, fb))) - /** - * Traverse each "side" of the structure with the given functions. - * - * Implements [[Bitraverse.bitraverse]]. - */ 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) - /** - * Given a mapping function in the `EitherT[F, A, ?]` context, applies it - * to `Right` values. - * - * This is very much like [[Applicative.ap]], except it's made to work with - * the right side, as needed by a bifunctor like `EitherT`. - */ 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))) - /** - * The monadic bind. - * - * Implements [[FlatMap.flatMap]]. - * - * Monad transforms like `EitherT` are essentially built to provide a `flatMap` - * implementation that reuses the underlying `F.flatMap` operation, hiding - * the complexity involved (in this case hiding the plumbing needed for - * dealing with `Left` values). - * - * @see [[flatMapF]] for mapping functions returning values in the - * `F[_]` context - */ def flatMap[AA >: A, D](f: B => EitherT[F, AA, D])(implicit F: Monad[F]): EitherT[F, AA, D] = EitherT(F.flatMap(value) { + case l @ Left(_) => F.pure(l.rightCast) case Right(b) => f(b).value - // N.B. pattern match doesn't do `case Left(_)` on purpose - case left => F.pure(eitherCast(left)) }) - /** - * A monadic bind variant that takes a parameter a mapping function that - * works with `F[_]` return types, instead of `EitherT`. - * - * It has the same properties as normal [[flatMap]], except that it accepts - * functions whose output isn't wrapped in `EitherT` already. - */ def flatMapF[AA >: A, D](f: B => F[Either[AA, D]])(implicit F: Monad[F]): EitherT[F, AA, D] = - flatMap(b => EitherT(f(b))) + flatMap(f andThen EitherT.apply) - /** - * Maps the underlying `F[_]` value with the provided function. - * - * This is a shorthand for mapping the [[value]] directly: - * {{{ - * et.transform(f) <-> EitherT(et.value.map(f)) - * }}} - */ def transform[C, D](f: Either[A, B] => Either[C, D])(implicit F: Functor[F]): EitherT[F, C, D] = EitherT(F.map(value)(f)) - /** - * Applies `flatMap` to the underlying `Either` value. - * - * Notice the equivalence: - * {{{ - * et.subflatMap(f) <-> EitherT(et.value.map(_.flatMap(f))) - * }}} - */ def subflatMap[AA >: A, D](f: B => Either[AA, D])(implicit F: Functor[F]): EitherT[F, AA, D] = transform(_.flatMap(f)) - /** - * Given the mapping function, returns a new `EitherT` that transform - * the `Right` values of the source with it. - * - * Implements [[Functor.map]]. - */ - def map[D](f: B => D)(implicit F: Functor[F]): EitherT[F, A, D] = - EitherT(F.map(value) { - case Right(b) => Right(f(b)) - case left => eitherCast(left) - }) + def map[D](f: B => D)(implicit F: Functor[F]): EitherT[F, A, D] = bimap(identity, f) /** * Modify the context `F` using transformation `f`. */ def mapK[G[_]](f: F ~> G): EitherT[G, A, B] = EitherT[G, A, B](f(value)) - /** - * Maps `Right` values generated by the source using the provided function, - * whose output is given in the `F[_]` context. - * - * This operation resembles [[flatMap]] or [[flatMapF]], except that the - * returned value is wrapped via [[EitherT.right]] so it lacks the ability - * of yielding `Left` values, being more like [[map]] in that sense. - * - * Example: - * {{{ - * def parseInt(n: String): EitherT[Eval, String, Int] = - * EitherT(Eval.always { - * try Right(n.toInt) - * catch { case NonFatal(_) => Left("invalid number") } - * }) - * - * // Yields Right(10000) on evaluation - * parseInt("9999").semiflatMap { n => - * Eval.always(n + 1) - * } - * }}} - */ def semiflatMap[D](f: B => F[D])(implicit F: Monad[F]): EitherT[F, A, D] = flatMap(b => EitherT.right(f(b))) - /** - * Applies the mapping function to the left "side". - * - * Like [[map]], except that it operates on `Left` values. - * - * @see [[bimap]] for mapping both sides - */ - def leftMap[C](f: A => C)(implicit F: Functor[F]): EitherT[F, C, B] = - bimap(f, identity) + def leftMap[C](f: A => C)(implicit F: Functor[F]): EitherT[F, C, B] = bimap(f, identity) - /** - * Binds the source to a new `EitherT` value using the provided function - * that operates on the left "side". - * - * This is like [[flatMap]] except that it operates on `Left` values, - * needed because `EitherT` is a bifunctor. - */ def leftFlatMap[BB >: B, D](f: A => EitherT[F, D, BB])(implicit F: Monad[F]): EitherT[F, D, BB] = EitherT(F.flatMap(value) { case Left(a) => f(a).value - // N.B. pattern match doesn't do `case Right(_)` on purpose - case right => F.pure(eitherCast(right)) + case r@Right(_) => F.pure(r.leftCast) }) - /** - * Maps `Left` values generated by the source using the provided function, - * whose output is given in the `F[_]` context. - * - * This is like [[semiflatMap]] except that it operates on `Left` values, - * needed because `EitherT` is a bifunctor. - */ def leftSemiflatMap[D](f: A => F[D])(implicit F: Monad[F]): EitherT[F, D, B] = EitherT(F.flatMap(value) { case Left(a) => F.map(f(a)) { d => Left(d) } - // N.B. pattern match doesn't do `case Right(_)` on purpose - case right => F.pure(eitherCast(right)) + case r@Right(_) => F.pure(r.leftCast) }) def compare(that: EitherT[F, A, B])(implicit o: Order[F[Either[A, B]]]): Int = @@ -1073,8 +138,7 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { def foldRight[C](lc: Eval[C])(f: (B, Eval[C]) => Eval[C])(implicit F: Foldable[F]): Eval[C] = F.foldRight(value, lc)((axb, lc) => axb.foldRight(lc)(f)) - def merge[AA >: A](implicit ev: B <:< AA, F: Functor[F]): F[AA] = - F.map(value)(_.fold(identity, ev.apply)) + def merge[AA >: A](implicit ev: B <:< AA, F: Functor[F]): F[AA] = F.map(value)(_.fold(identity, ev.apply)) /** * Similar to `Either#combine` but mapped over an `F` context. @@ -1142,8 +206,7 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { 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) + 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]`. @@ -1166,8 +229,7 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { * }}} * */ - def toNested: Nested[F, Either[A, ?], B] = - Nested[F, Either[A, ?], B](value) + def toNested: Nested[F, Either[A, ?], B] = Nested[F, Either[A, ?], B](value) /** * Transform this `EitherT[F, A, B]` into a `[[Nested]][F, Validated[A, ?], B]`. @@ -1202,7 +264,7 @@ object EitherT extends EitherTInstances { * Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics. */ private[data] final class LeftPartiallyApplied[B](val dummy: Boolean = true) extends AnyVal { - def apply[F[_], A](fa: F[A])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fa)(leftBox)) + def apply[F[_], A](fa: F[A])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fa)(Either.left)) } /** @@ -1238,7 +300,7 @@ object EitherT extends EitherTInstances { * Uses the [[http://typelevel.org/cats/guidelines.html#partially-applied-type-params Partially Applied Type Params technique]] for ergonomics. */ private[data] final class RightPartiallyApplied[A](val dummy: Boolean = true) extends AnyVal { - def apply[F[_], B](fb: F[B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fb)(rightBox)) + def apply[F[_], B](fb: F[B])(implicit F: Functor[F]): EitherT[F, A, B] = EitherT(F.map(fb)(Either.right)) } /** @@ -1526,33 +588,25 @@ private[data] trait EitherTMonad[F[_], L] extends Monad[EitherT[F, L, ?]] with E private[data] trait EitherTMonadErrorF[F[_], E, L] extends MonadError[EitherT[F, L, ?], E] with EitherTMonad[F, L] { implicit val F: MonadError[F, E] - override def handleError[A](fea: EitherT[F, L, A])(f: E => A): EitherT[F, L, A] = - fea.handleErrorF(f) - override def handleErrorWith[A](fea: EitherT[F, L, A])(f: E => EitherT[F, L, A]): EitherT[F, L, A] = - fea.handleErrorWithF(f) - override def attempt[A](fea: EitherT[F, L, A]): EitherT[F, L, Either[E, A]] = - fea.attemptF - override def redeem[A, B](fea: EitherT[F, L, A])(fe: E => B, fs: A => B): EitherT[F, L, B] = - fea.redeemF(fe, fs) - override def redeemWith[A, B](fea: EitherT[F, L, A])(fe: E => EitherT[F, L, B], fs: A => EitherT[F, L, B]): EitherT[F, L, B] = - fea.redeemWithF(fe, fs) - override def raiseError[A](e: E): EitherT[F, L, A] = - EitherT(F.raiseError(e)) + def handleErrorWith[A](fea: EitherT[F, L, A])(f: E => EitherT[F, L, A]): EitherT[F, L, A] = + EitherT(F.handleErrorWith(fea.value)(f(_).value)) + + def raiseError[A](e: E): EitherT[F, L, A] = EitherT(F.raiseError(e)) } private[data] trait EitherTMonadError[F[_], L] extends MonadError[EitherT[F, L, ?], L] with EitherTMonad[F, L] { - override def handleError[A](fla: EitherT[F, L, A])(f: L => A): EitherT[F, L, A] = - fla.handleError(f) - def handleErrorWith[A](fla: EitherT[F, L, A])(f: L => EitherT[F, L, A]): EitherT[F, L, A] = - fla.handleErrorWith(f) - override def redeem[A, B](fla: EitherT[F, L, A])(recover: L => B, map: A => B): EitherT[F, L, B] = - fla.redeem(recover, map) - override def redeemWith[A, B](fla: EitherT[F, L, A])(recover: L => EitherT[F, L, B], bind: A => EitherT[F, L, B]): EitherT[F, L, B] = - fla.redeemWith(recover, bind) - override def raiseError[A](e: L): EitherT[F, L, A] = - EitherT(F.pure(Left(e))) - override def attempt[A](fla: EitherT[F, L, A]): EitherT[F, L, Either[L, A]] = - fla.attempt + 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] = diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index d7889713bf..97d6afa780 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -4,7 +4,7 @@ package data import cats.Bifunctor import cats.arrow.FunctionK import cats.data.Validated.{Invalid, Valid} -import cats.internals.EitherUtil.{rightBox, leftBox} + import scala.annotation.tailrec /** Represents a right-biased disjunction that is either an `A`, or a `B`, or both an `A` and a `B`. @@ -49,7 +49,7 @@ sealed abstract class Ior[+A, +B] extends Product with Serializable { 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: Either[Either[A, B], (A, B)] = fold(a => Left(Left(a)), b => Left(Right(b)), (a, b) => Right((a, b))) - final def toEither: Either[A, B] = fold(leftBox, rightBox, (_, b) => Right(b)) + final def toEither: Either[A, B] = fold(Left(_), Right(_), (_, b) => Right(b)) final def toValidated: Validated[A, B] = fold(Invalid(_), Valid(_), (_, b) => Valid(b)) final def toOption: Option[B] = right final def toList: List[B] = right.toList diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 73f682c1e0..5c9874f9e7 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -1,7 +1,7 @@ package cats package instances -import cats.internals.EitherUtil.{leftBox, rightBox, eitherCast} +import cats.syntax.EitherUtil import cats.syntax.either._ import scala.annotation.tailrec @@ -10,8 +10,8 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { new Bitraverse[Either] { def bitraverse[G[_], A, B, C, D](fab: Either[A, B])(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[Either[C, D]] = fab match { - case Left(a) => G.map(f(a))(leftBox) - case Right(b) => G.map(g(b))(rightBox) + case Left(a) => G.map(f(a))(Left(_)) + case Right(b) => G.map(g(b))(Right(_)) } def bifoldLeft[A, B, C](fab: Either[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C = @@ -30,61 +30,53 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { // scalastyle:off method.length implicit def catsStdInstancesForEither[A]: MonadError[Either[A, ?], A] with Traverse[Either[A, ?]] = new MonadError[Either[A, ?], A] with Traverse[Either[A, ?]] { - override def pure[B](b: B): Either[A, B] = - Right(b) + def pure[B](b: B): Either[A, B] = Right(b) - override def flatMap[B, C](fa: Either[A, B])(f: B => Either[A, C]): Either[A, C] = - fa match { - case Right(b) => f(b) - case _ => eitherCast(fa) - } + def flatMap[B, C](fa: Either[A, B])(f: B => Either[A, C]): Either[A, C] = + fa.right.flatMap(f) - override def handleErrorWith[B](fea: Either[A, B])(f: A => Either[A, B]): Either[A, B] = + 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 } - override def raiseError[B](e: A): Either[A, B] = - Left(e) + 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 match { - case Right(b) => Right(f(b)) - case _ => eitherCast(fa) - } + fa.right.map(f) @tailrec - override def tailRecM[B, C](b: B)(f: B => Either[A, Either[B, 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 @ Left(_) => + left.rightCast[C] case Right(e) => e match { case Left(b1) => tailRecM(b1)(f) - case _ => eitherCast(e) + case right @ Right(_) => right.leftCast[A] } - case left => - eitherCast(left) } override def map2Eval[B, C, Z](fb: Either[A, B], fc: Eval[Either[A, C]])(f: (B, C) => Z): Eval[Either[A, Z]] = fb match { - case l @ Left(_) => Now(eitherCast(l)) + case l @ Left(_) => Now(EitherUtil.rightCast(l)) case Right(b) => fc.map(_.right.map(f(b, _))) } - override def traverse[F[_], B, C](fa: Either[A, B])(f: B => F[C])(implicit F: Applicative[F]): F[Either[A, C]] = + def traverse[F[_], B, C](fa: Either[A, B])(f: B => F[C])(implicit F: Applicative[F]): F[Either[A, C]] = fa match { - case left @ Left(_) => F.pure(eitherCast(left)) - case Right(b) => F.map(f(b))(rightBox) + case left @ Left(_) => F.pure(left.rightCast[C]) + case Right(b) => F.map(f(b))(Right(_)) } - override def foldLeft[B, C](fa: Either[A, B], c: C)(f: (C, B) => C): C = + def foldLeft[B, C](fa: Either[A, B], c: C)(f: (C, B) => C): C = fa match { case Left(_) => c case Right(b) => f(c, b) } - override def foldRight[B, C](fa: Either[A, B], lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = + def foldRight[B, C](fa: Either[A, B], lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = fa match { case Left(_) => lc case Right(b) => f(b, lc) diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index 83e6be877e..e144055ac2 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -1,8 +1,8 @@ package cats package instances -import cats.internals.FutureShims import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} trait FutureInstances extends FutureInstances1 { @@ -19,9 +19,17 @@ trait FutureInstances extends FutureInstances1 { 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[Either[Throwable, A]] = - FutureShims.attempt(fa) + fa.transformWith(r => Future.successful( + r match { + case Success(a) => Right(a) + case Failure(e) => Left(e) + } + )) override def redeemWith[A, B](fa: Future[A])(recover: Throwable => Future[B], bind: A => Future[B]): Future[B] = - FutureShims.redeemWith(fa)(recover, bind) + fa.transformWith { + case Success(a) => bind(a) + case Failure(e) => recover(e) + } override def recover[A](fa: Future[A])(pf: PartialFunction[Throwable, A]): Future[A] = fa.recover(pf) override def recoverWith[A](fa: Future[A])(pf: PartialFunction[Throwable, Future[A]]): Future[A] = diff --git a/core/src/main/scala/cats/internals/EitherUtil.scala b/core/src/main/scala/cats/internals/EitherUtil.scala deleted file mode 100644 index c75cddb2bf..0000000000 --- a/core/src/main/scala/cats/internals/EitherUtil.scala +++ /dev/null @@ -1,35 +0,0 @@ -package cats -package internals - -/** - * Internal API — Convenience functions for `Either`. - */ -private[cats] object EitherUtil { - @inline def leftCast[A, B, C](right: Right[A, B]): Either[C, B] = - right.asInstanceOf[Either[C, B]] - - @inline def rightCast[A, B, C](left: Left[A, B]): Either[A, C] = - left.asInstanceOf[Either[A, C]] - - @inline def eitherCast[A, B](either: Either[_, _]): Either[A, B] = - either.asInstanceOf[Either[A, B]] - - /** - * Internal API — reusable function for boxing values in `Right(_)`. - * To be used with `andThen`, e.g. `fa.map(f.andThen(rightBox))`. - */ - def rightBox[A, B]: B => Either[A, B] = - rightBoxRef.asInstanceOf[B => Either[A, B]] - - /** - * Internal API — reusable function for boxing values in `Left(_)`. - * To be used with `andThen`, e.g. `fa.map(f.andThen(leftBox))`. - */ - def leftBox[A, B]: A => Either[A, B] = - leftBoxRef.asInstanceOf[A => Either[A, B]] - - private[this] val rightBoxRef: Any => Either[Nothing, Nothing] = - a => new Right(a).asInstanceOf[Either[Nothing, Nothing]] - private[this] val leftBoxRef: Any => Either[Nothing, Nothing] = - a => new Left(a).asInstanceOf[Either[Nothing, Nothing]] -} diff --git a/core/src/main/scala/cats/syntax/either.scala b/core/src/main/scala/cats/syntax/either.scala index da3662f6e2..40bd310652 100644 --- a/core/src/main/scala/cats/syntax/either.scala +++ b/core/src/main/scala/cats/syntax/either.scala @@ -5,7 +5,6 @@ import cats.data.{EitherT, Ior, NonEmptyList, Validated, ValidatedNel} import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} import EitherSyntax._ -import cats.internals.EitherUtil trait EitherSyntax { implicit final def catsSyntaxEither[A, B](eab: Either[A, B]): EitherOps[A, B] = new EitherOps(eab) @@ -374,3 +373,11 @@ final class EitherIdOps[A](val obj: A) extends AnyVal { def rightNel[B]: Either[NonEmptyList[B], A] = Right(obj) } + +/** Convenience methods to use `Either` syntax inside `Either` syntax definitions. */ +private[cats] object EitherUtil { + def leftCast[A, B, C](right: Right[A, B]): Either[C, B] = + right.asInstanceOf[Either[C, B]] + def rightCast[A, B, C](left: Left[A, B]): Either[A, C] = + left.asInstanceOf[Either[A, C]] +} diff --git a/scalastyle-config.xml b/scalastyle-config.xml index baad869d2e..081d009534 100644 --- a/scalastyle-config.xml +++ b/scalastyle-config.xml @@ -1,6 +1,11 @@ Scalastyle standard configuration + + + + + diff --git a/tests/src/test/scala/cats/tests/RegressionSuite.scala b/tests/src/test/scala/cats/tests/RegressionSuite.scala index c4a5727e6c..b5b9f28803 100644 --- a/tests/src/test/scala/cats/tests/RegressionSuite.scala +++ b/tests/src/test/scala/cats/tests/RegressionSuite.scala @@ -131,4 +131,19 @@ class RegressionSuite extends CatsSuite { NonEmptyList.of(6,7,8).traverse_(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(1) } + + test("#2022 EitherT syntax no long works the old way") { + import data._ + + + EitherT.right[String](Option(1)).handleErrorWith((_: String) => EitherT.pure(2)) + + { + implicit val me = MonadError[EitherT[Option, String, ?], Unit] + EitherT.right[String](Option(1)).handleErrorWith((_: Unit) => EitherT.pure(2)) + } + + + } + } From a8ca54253b1bf1a5bce1ac2950169b8171c90220 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Wed, 13 Nov 2019 10:31:13 -0600 Subject: [PATCH 3/6] Move redeem to ApplicativeError --- .../main/scala/cats/ApplicativeError.scala | 32 +++++++++++++++++++ core/src/main/scala/cats/MonadError.scala | 32 ------------------- .../cats/laws/ApplicativeErrorLaws.scala | 3 ++ .../main/scala/cats/laws/MonadErrorLaws.scala | 3 -- .../discipline/ApplicativeErrorTests.scala | 3 +- .../laws/discipline/MonadErrorTests.scala | 1 - 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index ec4ab1bcda..04fad74f93 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -103,6 +103,38 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { def recoverWith[A](fa: F[A])(pf: PartialFunction[E, F[A]]): F[A] = handleErrorWith(fa)(e => pf.applyOrElse(e, raiseError)) + /** + * Returns a new value that transforms the result of the source, + * given the `recover` or `map` functions, which get executed depending + * on whether the result is successful or if it ends in error. + * + * This is an optimization on usage of [[attempt]] and [[map]], + * this equivalence being available: + * + * {{{ + * fa.redeem(fe, fs) <-> fa.attempt.map(_.fold(fe, fs)) + * }}} + * + * Usage of `redeem` subsumes [[handleError]] because: + * + * {{{ + * fa.redeem(fe, id) <-> fa.handleError(fe) + * }}} + * + * Implementations are free to override it in order to optimize + * error recovery. + * + * @see [[redeemWith]], [[attempt]] and [[handleError]] + * + * @param fa is the source whose result is going to get transformed + * @param recover is the function that gets called to recover the source + * in case of error + * @param map is the function that gets to transform the source + * in case of success + */ + def redeem[A, B](fa: F[A])(recover: E => B, f: A => B): F[B] = + handleError(map(fa)(f))(recover) + /** * Execute a callback on certain errors, then rethrow them. * Any non matching error is rethrown as well. diff --git a/core/src/main/scala/cats/MonadError.scala b/core/src/main/scala/cats/MonadError.scala index 01cec916eb..2d5eeb5a3c 100644 --- a/core/src/main/scala/cats/MonadError.scala +++ b/core/src/main/scala/cats/MonadError.scala @@ -66,38 +66,6 @@ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] { def rethrow[A, EE <: E](fa: F[Either[EE, A]]): F[A] = flatMap(fa)(_.fold(raiseError, pure)) - /** - * Returns a new value that transforms the result of the source, - * given the `recover` or `map` functions, which get executed depending - * on whether the result is successful or if it ends in error. - * - * This is an optimization on usage of [[attempt]] and [[map]], - * this equivalence being available: - * - * {{{ - * fa.redeem(fe, fs) <-> fa.attempt.map(_.fold(fe, fs)) - * }}} - * - * Usage of `redeem` subsumes [[handleError]] because: - * - * {{{ - * fa.redeem(fe, id) <-> fa.handleError(fe) - * }}} - * - * Implementations are free to override it in order to optimize - * error recovery. - * - * @see [[redeemWith]], [[attempt]] and [[handleError]] - * - * @param fa is the source whose result is going to get transformed - * @param recover is the function that gets called to recover the source - * in case of error - * @param map is the function that gets to transform the source - * in case of success - */ - def redeem[A, B](fa: F[A])(recover: E => B, map: A => B): F[B] = - redeemWith(fa)(recover.andThen(pure), map.andThen(pure)) - /** * Returns a new value that transforms the result of the source, * given the `recover` or `bind` functions, which get executed depending diff --git a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala index b0740ecb5f..fd013ed6b0 100644 --- a/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala @@ -45,6 +45,9 @@ trait ApplicativeErrorLaws[F[_], E] extends ApplicativeLaws[F] { def onErrorRaise[A](fa: F[A], e: E, fb: F[Unit]): IsEq[F[A]] = F.onError(F.raiseError[A](e)) { case err => fb } <-> F.map2(fb, F.raiseError[A](e))((_, b) => b) + + def redeemDerivedFromAttemptMap[A, B](fa: F[A], fe: E => B, fs: A => B): IsEq[F[B]] = + F.redeem(fa)(fe, fs) <-> F.map(F.attempt(fa))(_.fold(fe, fs)) } object ApplicativeErrorLaws { diff --git a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala index 3a7f7897e5..c8001e5b32 100644 --- a/laws/src/main/scala/cats/laws/MonadErrorLaws.scala +++ b/laws/src/main/scala/cats/laws/MonadErrorLaws.scala @@ -23,9 +23,6 @@ trait MonadErrorLaws[F[_], E] extends ApplicativeErrorLaws[F, E] with MonadLaws[ def rethrowAttempt[A](fa: F[A]): IsEq[F[A]] = F.rethrow(F.attempt(fa)) <-> fa - def redeemDerivedFromAttemptMap[A, B](fa: F[A], fe: E => B, fs: A => B): IsEq[F[B]] = - F.redeem(fa)(fe, fs) <-> F.map(F.attempt(fa))(_.fold(fe, fs)) - def redeemWithDerivedFromAttemptFlatMap[A, B](fa: F[A], fe: E => F[B], fs: A => F[B]): IsEq[F[B]] = F.redeemWith(fa)(fe, fs) <-> F.flatMap(F.attempt(fa))(_.fold(fe, fs)) } diff --git a/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala index e417b0f5de..85636ac363 100644 --- a/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ApplicativeErrorTests.scala @@ -53,7 +53,8 @@ trait ApplicativeErrorTests[F[_], E] extends ApplicativeTests[F] { laws.attemptFromEitherConsistentWithPure[A] _ ), "applicativeError onError pure" -> forAll(laws.onErrorPure[A] _), - "applicativeError onError raise" -> forAll(laws.onErrorRaise[A] _) + "applicativeError onError raise" -> forAll(laws.onErrorRaise[A] _), + "monadError redeem is derived from attempt and map" -> forAll(laws.redeemDerivedFromAttemptMap[A, B] _) ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala index 2371d88c52..4febbdb2a7 100644 --- a/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/MonadErrorTests.scala @@ -43,7 +43,6 @@ trait MonadErrorTests[F[_], E] extends ApplicativeErrorTests[F, E] with MonadTes "monadError adaptError pure" -> forAll(laws.adaptErrorPure[A] _), "monadError adaptError raise" -> forAll(laws.adaptErrorRaise[A] _), "monadError rethrow attempt" -> forAll(laws.rethrowAttempt[A] _), - "monadError redeem is derived from attempt and map" -> forAll(laws.redeemDerivedFromAttemptMap[A, B] _), "monadError redeemWith is derived from attempt and flatMap" -> forAll( laws.redeemWithDerivedFromAttemptFlatMap[A, B] _ ) From 138517060c04ee4e400c3d53d1eb60f3bf69690a Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Wed, 13 Nov 2019 10:52:00 -0600 Subject: [PATCH 4/6] Add redeem and redeemWith syntax methods --- core/src/main/scala/cats/syntax/applicativeError.scala | 3 +++ core/src/main/scala/cats/syntax/monadError.scala | 3 +++ 2 files changed, 6 insertions(+) diff --git a/core/src/main/scala/cats/syntax/applicativeError.scala b/core/src/main/scala/cats/syntax/applicativeError.scala index ed58a5cc2a..87d5e782f8 100644 --- a/core/src/main/scala/cats/syntax/applicativeError.scala +++ b/core/src/main/scala/cats/syntax/applicativeError.scala @@ -97,6 +97,9 @@ final class ApplicativeErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal def recoverWith(pf: PartialFunction[E, F[A]])(implicit F: ApplicativeError[F, E]): F[A] = F.recoverWith(fa)(pf) + def redeem[B](recover: E => B, f: A => B)(implicit F: ApplicativeError[F, E]): F[B] = + F.redeem[A, B](fa)(recover, f) + def onError(pf: PartialFunction[E, F[Unit]])(implicit F: ApplicativeError[F, E]): F[A] = F.onError(fa)(pf) diff --git a/core/src/main/scala/cats/syntax/monadError.scala b/core/src/main/scala/cats/syntax/monadError.scala index f6964dfeb0..7d6f152ce3 100644 --- a/core/src/main/scala/cats/syntax/monadError.scala +++ b/core/src/main/scala/cats/syntax/monadError.scala @@ -29,6 +29,9 @@ final class MonadErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal { def adaptError(pf: PartialFunction[E, E])(implicit F: MonadError[F, E]): F[A] = F.adaptError(fa)(pf) + + def redeemWith[B](recover: E => F[B], bind: A => F[B])(implicit F: MonadError[F, E]): F[B] = + F.redeemWith[A, B](fa)(recover, bind) } final class MonadErrorRethrowOps[F[_], E, A](private val fea: F[Either[E, A]]) extends AnyVal { From 9d9279720d2bbf1542db9c24dbcf0d1650e67083 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Wed, 13 Nov 2019 10:39:47 -0600 Subject: [PATCH 5/6] Add redeem and redeemWith syntax tests --- .../test/scala/cats/tests/SyntaxSuite.scala | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/src/test/scala/cats/tests/SyntaxSuite.scala index f2d20fa70a..6d21b7afba 100644 --- a/tests/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/src/test/scala/cats/tests/SyntaxSuite.scala @@ -379,7 +379,7 @@ object SyntaxSuite val done = a.tailRecM[F, B](a => returnValue) } - def testApplicativeError[F[_, _], E, A](implicit F: ApplicativeError[F[E, *], E]): Unit = { + def testApplicativeError[F[_, _], E, A, B](implicit F: ApplicativeError[F[E, *], E]): Unit = { type G[X] = F[E, X] val e = mock[E] @@ -402,12 +402,49 @@ object SyntaxSuite val pfegea = mock[PartialFunction[E, G[A]]] val gea4 = ga.recoverWith(pfegea) + + val eb = mock[E => B] + val ab = mock[A => B] + val gb: G[B] = gea.redeem(eb, ab) } def testApplicativeErrorSubtype[F[_], A](implicit F: ApplicativeError[F, CharSequence]): Unit = { val fea = "meow".raiseError[F, A] } + def testMonadError[F[_, _], E, A, B](implicit F: MonadError[F[E, *], E]): Unit = { + type G[X] = F[E, X] + + val e = mock[E] + val ga = e.raiseError[G, A] + + val gea = mock[G[A]] + + val ea = mock[E => A] + val gea1 = ga.handleError(ea) + + val egea = mock[E => G[A]] + val gea2 = ga.handleErrorWith(egea) + + val gxea = ga.attempt + + val gxtea = ga.attemptT + + val pfea = mock[PartialFunction[E, A]] + val gea3 = ga.recover(pfea) + + val pfegea = mock[PartialFunction[E, G[A]]] + val gea4 = ga.recoverWith(pfegea) + + val eb = mock[E => B] + val ab = mock[A => B] + val gb: G[B] = gea.redeem(eb, ab) + + val efb = mock[E => G[B]] + val afb = mock[A => G[B]] + val gb2: G[B] = gea.redeemWith(efb, afb) + } + def testNested[F[_], G[_], A]: Unit = { val fga: F[G[A]] = mock[F[G[A]]] From 6ba59df93c70a63899bb2656c4ca55a67f484998 Mon Sep 17 00:00:00 2001 From: Travis Brown Date: Wed, 13 Nov 2019 11:35:09 -0600 Subject: [PATCH 6/6] Fix redeem API docs --- core/src/main/scala/cats/ApplicativeError.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/ApplicativeError.scala b/core/src/main/scala/cats/ApplicativeError.scala index 04fad74f93..7831e42677 100644 --- a/core/src/main/scala/cats/ApplicativeError.scala +++ b/core/src/main/scala/cats/ApplicativeError.scala @@ -124,7 +124,7 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { * Implementations are free to override it in order to optimize * error recovery. * - * @see [[redeemWith]], [[attempt]] and [[handleError]] + * @see [[MonadError.redeemWith]], [[attempt]] and [[handleError]] * * @param fa is the source whose result is going to get transformed * @param recover is the function that gets called to recover the source