-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Behaviour of MonadCancelThrow[EitherT[IO, Throwable, *]]
#4308
Comments
What's the thing overriding |
reflect.runtime.universe.reify { bar(a) }val res0: reflect.runtime.universe.Expr[cats.data.EitherT[cats.effect.IO,Throwable,Unit]] = Expr[cats.data.EitherT[cats.effect.IO,Throwable,Unit]](Playground.bar(Playground.a)(EitherT.catsDataMonadErrorForEitherT(IO.asyncForIO), Console.catsEitherTConsole(IO.consoleForIO, IO.asyncForIO))) |
Oh I see what's going on. This is super annoying. So inside of protected def delegate: MonadError[EitherT[F, E0, *], E] =
EitherT.catsDataMonadErrorFForEitherT[F, E, E0] Viewed locally, this is kind of the only thing we could have (more on this in a moment), since we don't know anything specific about This is fixable by creating some additional disambiguation pathways all the way down the |
Runnable reproduction. //> using lib "org.typelevel::cats-effect:3.3.12"
//> using option "-Ykind-projector"
import cats._
import cats.data._
import cats.effect._
import cats.effect.std._
import cats.syntax.all._
import cats.effect.unsafe.implicits._
val a: EitherT[IO, Throwable, Unit] =
EitherT.leftT[IO, Unit](new Exception("Yooo"))
val console = Console[EitherT[IO, Throwable, *]]
def foo(fa: EitherT[IO, Throwable, Unit]): EitherT[IO, Throwable, Unit] =
fa.flatMap(_ => console.println("not printed"))
.handleError(_ => ())
def bar[F[_]: MonadThrow: Console](fa: F[Unit]): F[Unit] =
fa.flatMap(_ => Console[F].println("not printed"))
.handleError(_ => ())
def baz[F[_]: MonadCancelThrow: Console](fa: F[Unit]): F[Unit] =
fa.flatMap(_ => Console[F].println("not printed"))
.handleError(_ => ())
def x = foo(a).value.unsafeRunSync()
def y = bar(a).value.unsafeRunSync()
def z = baz(a).value.unsafeRunSync()
@main def main =
println(x)
println(y)
println(z) |
So I tried doing the fix as described in #4308 but quickly ran into law failures in typelevel/cats-effect#3028. Then I tried various other things without success. My current feeling is it's not possible to fix this by introducing another implementation of The reason Cats can have two implementations is because you can implement This is not the case for The catch is that there are laws that relate the error channel of a Since I made some attempts to hack around this in typelevel/cats-effect#3028 essentially by trying to "collapse" the two error channels into a single one, but this breaks down as well. |
I think @armanbilge has convinced me that this is fundamental. I think the best we can do is probably swap the precedence order in Cats (which is arbitrarily for the outer rather than the inner) to make this type of thing less jarring. In theory this would fix The problem ultimately stems from the fact that In the event that we have a We should relocate this issue to Cats, IMO. |
MonadCancelThrow[EitherT[IO, Throwable, *]]
I started looking into this, and as I suspected it is entangled with #2237. |
This issue sounds somewhat familiar, and apologies if I forgot the exact discussion, however I stumbled on it in the wild in the context of typelevel/fs2#2895 and found it pretty confusing.
In particular:
MonadThrow
andMonadCancelThrow
is inconsistentMonadCancelThrow
is weird: excluding cancelation, if something short circuits aflatMap
I expect to be able tohandleError
it. Ofc this expectation is broken when you have two error channels, but MonadThrow works around itThe text was updated successfully, but these errors were encountered: