(value: EitherT[Future, A :+: B :+: CNil, Result]).ignore(identity[B])
results in an EitherT[Future, A, Result]
given B
is a Throwable
.MonadError[Future, Throwable]
.
ErrorTransCoproductOps
?
trait Raisable[F[_, _], E] {
def raise[L, R](f: F[L, R])(e: => E): F[L, R]
}
object Raisable {
implicit def eitherTRaisable[F[_], E](implicit monadError: MonadError[F, E]) = new Raisable[EitherT[F, _, _], E] {
def raise[L, R](f: EitherT[F, L, R])(e: => E): EitherT[F, L, R] = f.flatMapF(_ => monadError.raiseError(e))
}
}
Got it working :) Let me know what you think
scala.DummyImplicit
instead of DummyParam
?
@kag0 nice Didn't know about DummyImplicit :D
I'm wondering what are you going to do with the errors you specified if it's not a throwable? (I don't really want to encourage people do silently ignore errors..)
If you want to transform particular errors into the other type, you can use handleError
to handle a subset of the errors and turn them into some other error type? (The result type should be correctly inferred)
Note that I'm thinking of changing the names from handle1
, handleSome
to mapError1
and mapSomeError
Throwable
, or maybe the user is possesed to have an Either[FatalE, Either[ExpectedE, Result]]
or something.
mapSomeAndDie
but it's probably to keep the API surface small for now.F[FatalE :+: Expected E :+: CNil, Result]
to F[FatalE :+: CNil, F[ExpectedE, Result]
? You can use flatmapError
for that (it's currently named handleSomeWith
)
A concrete example might be like
val before: Either[FatalE, Either[ExpectedE :+: FatalE :+: CNil, Result]] = ???
val after: Either[FatalE, Either[ExpectedE :+: CNil, Result]] = ???
so you're suggesting that a user could do
val after = before.flatMap(_.flatmapError((e: ExpectedE) => Right(Left(e))))
?
trait ErrorTransThrow[F[_, _], E] extends ErrorTrans[F] {
def extractAndThrow[L, R, LL](in: F[L, R])(extractUnhandled: L => Either[E, LL]): F[LL, R]
}
implicit def zioErrorTrans[Env] =
new ErrorTransThrow[ZIO[Env, *, *], Throwable] {
override def extractAndThrow[L, R, LL](in: ZIO[Env, L, R])(
extractUnhandled: L => Either[Throwable, LL],
): ZIO[Env, LL, R] = in.catchAll { errors =>
extractUnhandled(errors) match {
case Left(throwable) => ZIO.die(throwable)
case Right(handledErrors) => ZIO.fail(handledErrors)
}
}
}
implicit def eitherTErrorTrans[G[_], E](implicit gMonadError: MonadError[G, E]): ErrorTransThrow[EitherT[G, *, *], E] =
new ErrorTransThrow[EitherT[G, *, *], E] {
override def extractAndThrow[L, R, LL](in: EitherT[G, L, R])(
extractUnhandled: L => Either[E, LL]
) = in.leftFlatMap { l =>
extractUnhandled(l) match {
case Left(e) => EitherT(gMonadError.raiseError(e))
case Right(ll) => pureError(ll)
}
}
}
I think there's a generalization hidden in those examples somewhere. It feels like some sort of leftFlatten
(Although you're not flattening anything)
I get what you're trying to do, but how did you end up with Either[.., Either[..]] in the first place?
I'll need to spend some time thinking about the laws for these typeclasses, for some reason I don't think the EitherT and ZIO instance in your example is behaving the same
This is the definition of MonadError for EitherT
implicit def catsDataMonadErrorFForEitherT[F[_], E, L](
implicit FE0: MonadError[F, E]
): MonadError[EitherT[F, L, ?], E]
So let's say your monad is EitherT[cats.effect.IO, MyError, Unit], the MonadError instance you have is MonadError[IO, Throwable]
MyError