Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move adaptError #3148

Merged
merged 7 commits into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions core/src/main/scala/cats/ApplicativeError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,33 @@ 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))

/**
* Transform certain errors using `pf` and rethrow them.
* Non matching errors and successful values are not affected by this function.
*
* Example:
* {{{
* scala> import cats._, implicits._
*
* scala> def pf: PartialFunction[String, String] = { case "error" => "ERROR" }
*
* scala> ApplicativeError[Either[String, *], String].adaptError("error".asLeft[Int])(pf)
* res0: Either[String,Int] = Left(ERROR)
*
* scala> ApplicativeError[Either[String, *], String].adaptError("err".asLeft[Int])(pf)
* res1: Either[String,Int] = Left(err)
*
* scala> ApplicativeError[Either[String, *], String].adaptError(1.asRight[String])(pf)
* res2: Either[String,Int] = Right(1)
* }}}
*
* The same function is available in `ApplicativeErrorOps` as `adaptErr` - it cannot have the same
* name because this would result in ambiguous implicits due to `adaptError`
* having originally been included in the `MonadError` API and syntax.
*/
def adaptError[A](fa: F[A])(pf: PartialFunction[E, E]): F[A] =
recoverWith(fa)(pf.andThen(raiseError[A] _))

/**
* Returns a new value that transforms the result of the source,
* given the `recover` or `map` functions, which get executed depending
Expand Down
27 changes: 0 additions & 27 deletions core/src/main/scala/cats/MonadError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,6 @@ trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] {
def ensureOr[A](fa: F[A])(error: A => E)(predicate: A => Boolean): F[A] =
flatMap(fa)(a => if (predicate(a)) pure(a) else raiseError(error(a)))

/**
* Transform certain errors using `pf` and rethrow them.
* Non matching errors and successful values are not affected by this function.
*
* Example:
* {{{
* scala> import cats._, implicits._
*
* scala> def pf: PartialFunction[String, String] = { case "error" => "ERROR" }
*
* scala> "error".asLeft[Int].adaptError(pf)
* res0: Either[String,Int] = Left(ERROR)
*
* scala> "err".asLeft[Int].adaptError(pf)
* res1: Either[String,Int] = Left(err)
*
* scala> 1.asRight[String].adaptError(pf)
* res2: Either[String,Int] = Right(1)
* }}}
*
* The same function is available in `ApplicativeErrorOps` as `adaptErr` - it cannot have the same
* name because this would result in ambiguous implicits. `adaptError` will be moved from MonadError to
* ApplicativeError in Cats 2.0: see [[https://github.com/typelevel/cats/issues/2685]]
*/
def adaptError[A](fa: F[A])(pf: PartialFunction[E, E]): F[A] =
recoverWith(fa)(pf.andThen(raiseError[A] _))

/**
* Inverse of `attempt`
*
Expand Down
6 changes: 2 additions & 4 deletions core/src/main/scala/cats/syntax/applicativeError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,7 @@ final class ApplicativeErrorOps[F[_], E, A](private val fa: F[A]) extends AnyVal
* }}}
*
* This is the same as `MonadErrorOps#adaptError`. It cannot have the same name because
* this would result in ambiguous implicits. `adaptError` will be moved from `MonadError`
* to `ApplicativeError` in Cats 2.0: see [[https://github.com/typelevel/cats/issues/2685]]
* this would result in ambiguous implicits.
*/
def adaptErr(pf: PartialFunction[E, E])(implicit F: ApplicativeError[F, E]): F[A] =
F.recoverWith(fa)(pf.andThen(F.raiseError[A] _))
def adaptErr(pf: PartialFunction[E, E])(implicit F: ApplicativeError[F, E]): F[A] = F.adaptError(fa)(pf)
}
6 changes: 6 additions & 0 deletions laws/src/main/scala/cats/laws/ApplicativeErrorLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ 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 adaptErrorPure[A](a: A, f: E => E): IsEq[F[A]] =
F.adaptError(F.pure(a)) { case x => f(x) } <-> F.pure(a)

def adaptErrorRaise[A](e: E, f: E => E): IsEq[F[A]] =
F.adaptError(F.raiseError[A](e)) { case x => f(x) } <-> F.raiseError(f(e))

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))
}
Expand Down
6 changes: 0 additions & 6 deletions laws/src/main/scala/cats/laws/MonadErrorLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ trait MonadErrorLaws[F[_], E] extends ApplicativeErrorLaws[F, E] with MonadLaws[
def monadErrorEnsureOrConsistency[A](fa: F[A], e: A => E, p: A => Boolean): IsEq[F[A]] =
F.ensureOr(fa)(e)(p) <-> F.flatMap(fa)(a => if (p(a)) F.pure(a) else F.raiseError(e(a)))

def adaptErrorPure[A](a: A, f: E => E): IsEq[F[A]] =
F.adaptError(F.pure(a)) { case x => f(x) } <-> F.pure(a)

def adaptErrorRaise[A](e: E, f: E => E): IsEq[F[A]] =
F.adaptError(F.raiseError[A](e)) { case x => f(x) } <-> F.raiseError(f(e))

def rethrowAttempt[A](fa: F[A]): IsEq[F[A]] =
F.rethrow(F.attempt(fa)) <-> fa

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ trait ApplicativeErrorTests[F[_], E] extends ApplicativeTests[F] {
),
"applicativeError onError pure" -> forAll(laws.onErrorPure[A] _),
"applicativeError onError raise" -> forAll(laws.onErrorRaise[A] _),
"monadError redeem is derived from attempt and map" -> forAll(laws.redeemDerivedFromAttemptMap[A, B] _)
"applicativeError adaptError pure" -> forAll(laws.adaptErrorPure[A] _),
"applicativeError adaptError raise" -> forAll(laws.adaptErrorRaise[A] _),
"applicativeError redeem is derived from attempt and map" -> forAll(laws.redeemDerivedFromAttemptMap[A, B] _)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ trait MonadErrorTests[F[_], E] extends ApplicativeErrorTests[F, E] with MonadTes
"monadError left zero" -> forAll(laws.monadErrorLeftZero[A, B] _),
"monadError ensure consistency" -> forAll(laws.monadErrorEnsureConsistency[A] _),
"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 redeemWith is derived from attempt and flatMap" -> forAll(
laws.redeemWithDerivedFromAttemptFlatMap[A, B] _
Expand Down
6 changes: 6 additions & 0 deletions tests/src/test/scala/cats/tests/SyntaxSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,9 @@ object SyntaxSuite
val eb = mock[E => B]
val ab = mock[A => B]
val gb: G[B] = gea.redeem(eb, ab)

val pfee = mock[PartialFunction[E, E]]
val gea5 = gea.adaptErr(pfee)
}

def testApplicativeErrorSubtype[F[_], A](implicit F: ApplicativeError[F, CharSequence]): Unit = {
Expand Down Expand Up @@ -454,6 +457,9 @@ object SyntaxSuite
val efb = mock[E => G[B]]
val afb = mock[A => G[B]]
val gb2: G[B] = gea.redeemWith(efb, afb)

val pfee = mock[PartialFunction[E, E]]
val gea5 = gea.adaptError(pfee)
}

def testNested[F[_], G[_], A]: Unit = {
Expand Down