-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add ErrorControl #2231
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
Add ErrorControl #2231
Changes from 8 commits
60f6dc4
8e3b5c9
60b873d
f1fa2fe
39460da
bc6404d
f35dd7f
6cd9eb0
d255b84
b1b8a54
81f6ee6
49c2525
5ae4721
0ec07a6
4533a83
86ff72a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package cats | ||
|
|
||
| import cats.data.EitherT | ||
|
|
||
| trait ErrorControl[F[_], G[_], E] extends Serializable { | ||
| val monadErrorF: MonadError[F, E] | ||
|
||
| val monadG: Monad[G] | ||
|
|
||
| def controlError[A](fa: F[A])(f: E => G[A]): G[A] | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like a The name might not be great, but I would love to see this and have syntax for it. |
||
| def accept[A](ga: G[A]): F[A] | ||
|
|
||
| def trial[A](fa: F[A]): G[Either[E, A]] = | ||
| intercept(monadErrorF.map(fa)(Right(_): Either[E, A]))(Left(_)) | ||
|
|
||
| def trialT[A](fa: F[A]): EitherT[G, E, A] = | ||
| EitherT(trial(fa)) | ||
|
|
||
| def intercept[A](fa: F[A])(f: E => A): G[A] = | ||
| controlError(fa)(f andThen monadG.pure) | ||
|
|
||
| def absolve[A](gea: G[Either[E, A]]): F[A] = | ||
| monadErrorF.flatMap(accept(gea))(_.fold(monadErrorF.raiseError, monadErrorF.pure)) | ||
|
|
||
| def assure[A](ga: G[A])(error: => E)(predicate: A => Boolean): F[A] = | ||
| assureOr(ga)(_ => error)(predicate) | ||
|
|
||
| def assureOr[A](ga: G[A])(error: A => E)(predicate: A => Boolean): F[A] = | ||
|
||
| monadErrorF.flatMap(accept(ga))(a => | ||
| if (predicate(a)) monadErrorF.pure(a) else monadErrorF.raiseError(error(a))) | ||
|
|
||
| } | ||
|
|
||
| object ErrorControl { | ||
|
|
||
| def apply[F[_], G[_], E](implicit ev: ErrorControl[F, G, E]): ErrorControl[F, G, E] = ev | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,6 @@ package cats | |
| package data | ||
|
|
||
| import cats.arrow.{Profunctor, Strong} | ||
|
|
||
| import cats.syntax.either._ | ||
|
|
||
| /** | ||
|
|
@@ -247,6 +246,23 @@ private[data] sealed abstract class IndexedStateTInstances extends IndexedStateT | |
| implicit def catsDataAlternativeForIndexedStateT[F[_], S](implicit FM: Monad[F], | ||
| FA: Alternative[F]): Alternative[IndexedStateT[F, S, S, ?]] with Monad[IndexedStateT[F, S, S, ?]] = | ||
| new IndexedStateTAlternative[F, S] { implicit def F = FM; implicit def G = FA } | ||
|
|
||
| implicit def catsErrorControlForStateT[F[_], G[_], S, E] | ||
| (implicit E: ErrorControl[F, G, E], M: Monad[G]): ErrorControl[StateT[F, S, ?], StateT[G, S, ?], E] = | ||
|
||
| new ErrorControl[StateT[F, S, ?], StateT[G, S, ?], E] { | ||
| implicit val F: MonadError[F, E] = E.monadErrorF | ||
|
|
||
| val monadErrorF: MonadError[StateT[F, S, ?], E] = IndexedStateT.catsDataMonadErrorForIndexedStateT | ||
| val monadG: Monad[StateT[G, S, ?]] = IndexedStateT.catsDataMonadForIndexedStateT(M) | ||
|
|
||
| def accept[A](ga: StateT[G, S, A]): StateT[F, S, A] = ga.mapK(new (G ~> F) { | ||
| def apply[T](ga: G[T]): F[T] = E.accept(ga) | ||
| }) | ||
|
|
||
| def controlError[A](fa: StateT[F, S, A])(f: E => StateT[G, S, A]): StateT[G, S, A] = | ||
| IndexedStateT(s => E.controlError(fa.run(s))(e => f(e).run(s))) | ||
|
|
||
| } | ||
| } | ||
|
|
||
| private[data] sealed abstract class IndexedStateTInstances1 extends IndexedStateTInstances2 { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,6 +217,22 @@ private[data] sealed abstract class OptionTInstances extends OptionTInstances0 { | |
| implicit def catsDataMonadForOptionT[F[_]](implicit F0: Monad[F]): Monad[OptionT[F, ?]] = | ||
| new OptionTMonad[F] { implicit val F = F0 } | ||
|
|
||
| implicit def catsEndeavorForOptionT[F[_]: Monad, E]: ErrorControl[OptionT[F, ?], F, Unit] = | ||
|
||
| new ErrorControl[OptionT[F, ?], F, Unit] { | ||
| val monadErrorF: MonadError[OptionT[F, ?], Unit] = catsDataMonadErrorUnitForOptionT | ||
| val monadG: Monad[F] = Monad[F] | ||
|
|
||
| def controlError[A](fa: OptionT[F, A])(f: Unit => F[A]): F[A] = | ||
| Monad[F].flatMap(fa.value) { | ||
| case Some(a) => monadG.pure(a) | ||
| case None => f(()) | ||
| } | ||
|
|
||
| def accept[A](ga: F[A]): OptionT[F, A] = | ||
| OptionT.liftF(ga) | ||
|
|
||
| } | ||
|
|
||
| implicit def catsDataFoldableForOptionT[F[_]](implicit F0: Foldable[F]): Foldable[OptionT[F, ?]] = | ||
| new OptionTFoldable[F] { implicit val F = F0 } | ||
|
|
||
|
|
@@ -249,6 +265,9 @@ private[data] sealed abstract class OptionTInstances0 extends OptionTInstances1 | |
|
|
||
| private[data] sealed abstract class OptionTInstances1 extends OptionTInstances2 { | ||
|
|
||
| implicit def catsDataMonadErrorUnitForOptionT[F[_]](implicit F0: Monad[F]): MonadError[OptionT[F, ?], Unit] = | ||
| new OptionTMonadErrorUnit[F] { implicit val F = F0 } | ||
|
|
||
| implicit def catsDataMonoidKForOptionT[F[_]](implicit F0: Monad[F]): MonoidK[OptionT[F, ?]] = | ||
| new OptionTMonoidK[F] { implicit val F = F0 } | ||
|
|
||
|
|
@@ -297,6 +316,18 @@ private trait OptionTMonadError[F[_], E] extends MonadError[OptionT[F, ?], E] wi | |
| OptionT(F.handleErrorWith(fa.value)(f(_).value)) | ||
| } | ||
|
|
||
| private trait OptionTMonadErrorUnit[F[_]] extends MonadError[OptionT[F, ?], Unit] with OptionTMonad[F] { | ||
| implicit def F: Monad[F] | ||
|
|
||
| def raiseError[A](e: Unit): OptionT[F, A] = OptionT.none | ||
|
|
||
| def handleErrorWith[A](fa: OptionT[F, A])(f: Unit => OptionT[F, A]): OptionT[F, A] = | ||
| OptionT(F.flatMap(fa.value) { | ||
| case s @ Some(_) => F.pure(s) | ||
| case None => f(()).value | ||
| }) | ||
| } | ||
|
|
||
| private trait OptionTContravariantMonoidal[F[_]] extends ContravariantMonoidal[OptionT[F, ?]] { | ||
| def F: ContravariantMonoidal[F] | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| package cats | ||
| package syntax | ||
|
|
||
| import cats.data.EitherT | ||
|
|
||
|
|
||
| trait ErrorControlSyntax { | ||
| implicit final def catsSyntaxErrorControlF[F[_], E, A] | ||
| (fa: F[A])(implicit F: MonadError[F, E]): ErrorControlFOps[F, E, A] = | ||
| new ErrorControlFOps[F, E, A](fa) | ||
|
|
||
| implicit final def catsSyntaxErrorControlG[G[_], A] | ||
| (ga: G[A])(implicit G: Applicative[G]): ErrorControlGOps[G, A] = | ||
| new ErrorControlGOps[G, A](ga) | ||
|
|
||
| implicit final def catsSyntaxErrorControlEither[G[_], E, A] | ||
| (gea: G[Either[E, A]])(implicit G: Applicative[G]): ErrorControlEitherOps[G, E, A] = | ||
| new ErrorControlEitherOps[G, E, A](gea) | ||
|
|
||
| } | ||
|
|
||
|
|
||
| final class ErrorControlFOps[F[_], E, A](val fa: F[A]) extends AnyVal { | ||
|
|
||
| def controlError[G[_]](f: E => G[A])(implicit E: ErrorControl[F, G, E]): G[A] = | ||
| E.controlError(fa)(f) | ||
|
|
||
| def trial[G[_]](implicit E: ErrorControl[F, G, E]): G[Either[E, A]] = | ||
| E.trial(fa) | ||
|
|
||
| def trialT[G[_]](implicit E: ErrorControl[F, G, E]): EitherT[G, E, A] = | ||
| E.trialT(fa) | ||
|
|
||
| def intercept[G[_]](f: E => A)(implicit E: ErrorControl[F, G, E]): G[A] = | ||
| E.intercept(fa)(f) | ||
| } | ||
|
|
||
| final class ErrorControlGOps[G[_], A](val ga: G[A]) extends AnyVal { | ||
| def assure[F[_]]: AssurePartiallyApplied[F, G, A] = new AssurePartiallyApplied[F, G, A](ga) | ||
|
|
||
| def assureOr[F[_]]: AssureOrPartiallyApplied[F, G, A] = new AssureOrPartiallyApplied[F, G, A](ga) | ||
|
|
||
| def accept[F[_]]: AcceptPartiallyApplied[F, G, A] = new AcceptPartiallyApplied[F, G, A](ga) | ||
| } | ||
|
|
||
| final class ErrorControlEitherOps[G[_], E, A](val gea: G[Either[E, A]]) extends AnyVal { | ||
| def absolve[F[_]](implicit E: ErrorControl[F, G, E]): F[A] = | ||
| E.absolve(gea) | ||
| } | ||
|
|
||
| private[syntax] final class AssurePartiallyApplied[F[_], G[_], A](val ga: G[A]) extends AnyVal { | ||
| def apply[E](error: => E) | ||
| (predicate: A => Boolean) | ||
| (implicit E: ErrorControl[F, G, E]): F[A] = | ||
| E.assure(ga)(error)(predicate) | ||
| } | ||
|
|
||
| private[syntax] final class AssureOrPartiallyApplied[F[_], G[_], A](val ga: G[A]) extends AnyVal { | ||
| def apply[E](error: A => E) | ||
| (predicate: A => Boolean) | ||
| (implicit E: ErrorControl[F, G, E]): F[A] = | ||
| E.assureOr(ga)(error)(predicate) | ||
| } | ||
|
|
||
| private[syntax] final class AcceptPartiallyApplied[F[_], G[_], A](val ga: G[A]) extends AnyVal { | ||
| def apply[E](implicit E: ErrorControl[F, G, E]): F[A] = | ||
| E.accept(ga) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to use abstract types as "output types". It seems to me we might want
G[_]to be an abstract type here, since we might often want to write algorithms to work in F go through G, but return an F. Then we could accept an implicitErrorControl[F, E]to do that, which might be nice.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that as well, see my recent issue in parallel, though I'm not 100% sure if it applies here, have to think about it more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we'd be able to express these https://github.com/typelevel/cats/pull/2231/files#diff-aadaecd5f11de32b7cdacd702c9a5393R38 maybe you know a way?