-
Notifications
You must be signed in to change notification settings - Fork 520
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
Concurrent
implying Parallel
breaks coherence
#1645
Comments
Concurrent
implying Parallel
breaks coherence
Does this actually result in different semantics though? They should be consistent even though they're different instances. |
I'm afraid it does 😞 . Here's an ammonite script that shows the difference in behaviour // scala 2.13.4
import $ivy.`org.typelevel::cats-effect:3.0.0-M5`
import cats._
import cats.effect._
import cats.syntax.all._
import cats.data.EitherT
import scala.concurrent.duration._
// Int being semigroupal and IO having a Parallel instance
// leads to EitherT[IO, Int, *] getting a parallel instance
// from cats-core that respects both layers
val foo: EitherT[IO, Int, Unit] =
EitherT.liftF[IO, Int, Unit](IO.sleep(1.second)) *>
EitherT.fromEither[IO](1.asLeft[Unit])
val withStandardInstance: IO[Unit] = {
(foo, foo).parTupled.value.flatMap(IO.println)
}
val withCEInstance: IO[Unit] = {
import cats.effect.implicits._
(foo, foo).parTupled.value.flatMap(IO.println)
}
import cats.effect.unsafe.implicits.global
(withStandardInstance &> withCEInstance).unsafeRunTimed(1.5.second) prints
|
Why do you have to make me sad, Oli? |
Concurrent
implying Parallel
breaks coherence Concurrent
implying Parallel
breaks coherence
So in a way, this is very similar to the #1576 issue: the effect stack has a desired side-channel semantic which is inconsistent with the semantics which simply emerge from the primitives as defined. However, it's also being done for exactly the same reason: I think it is very, very intuitive that In order to get commutativity here, we would need to get it, effectively, out of EitherT(F.both(fa.value, fb.value).map(_.tupled))
Which brings us all the way back to The incoherence is very bad and we can't have that. If we take it as a given that we need to resolve this, then there are really only two options:
Hear me out. While it is intuitive that Similarly, this would automatically provide a resolution to #1576 (which boils down to the exact same problem), since we would no longer need to care about the non-commutative nature of finalizer order. Tldr, I think |
I'm failing to understand what commutativity has to do with the issue, and #1576 does not give enough context to understand.
I'm not seeing that it does. Commutativity matters to parallel for a subset of its operations. Moreover, the Applicative type tied to val foo: EitherT[IO, List[Int], Unit] =
EitherT.liftF[IO, List[Int], Unit](IO.sleep(1.second)) *> EitherT.fromEither[IO](List(1).asLeft[Unit]) leads to the following being printed Left(List(1))
Left(List(1, 1))
It is, but it is also very intuitive to me that
Just trying to make your future-self less sad is all 😄 |
Right, I'm starting to link the dots after looking at gitter. Still would like some more explanation around what commutativity has to do with this.
I think that now that cats-core typeclass instances for stdlib types are no-longer orphan, that particular issue would be greatly mitigated if So to answer your question to me, the precedent☝️ leads me to think that the presence of a |
Did you close this issue by accident? |
I did yeah, that'll teach me to watch github from my phone ... |
You're correct. I went through and read it last night but didn't loop back here. This discovery honestly leads me to the following pair of conclusions:
Why is that intuitive? It doesn't happen for IO.raiseError(new Exception("1")) *> IO.raiseError(new Exception("2")) // => error: "1" You're basically asking
I'm not convinced, though. We could take it into account… in Honestly, it feels more like the |
PR is at typelevel/cats#3777, please everyone take a look. |
Replying in typelevel/cats#3776 |
This I agree with. I do not agree with your solution of changing the existing semantics of EitherT's parallel.
I did mention it was intuitive "on The only reason for which I'd personally use In contrast, |
Using CE 3.0.0-M5
See gitter discussion
In case of monad stacks, the potential parallel nature of each layer should be respected : par-traversing EitherT when the error type is semigroupal and the
F
layer succeeds but severalEithers
fail ought to give an aggregate of errors.This behaviour is encoded in cats here. However, importing
cats.effect.implicits._
results in theParallel
instance coming fromGenSpawn
to take precedence. This instance is unaware of the potential layering of the effect, and therefore cannot respect it.Here's an ammonite snippet showing the problem :
res6
andres8
point to different instancesThe text was updated successfully, but these errors were encountered: