From 5c07d06ba1250b3e31dcb1a8df70b3b6bca4404a Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Sun, 7 Feb 2021 22:33:44 -0700 Subject: [PATCH 1/3] Removed and replaced inconsistent Parallel derivation for EitherT --- core/src/main/scala/cats/data/EitherT.scala | 33 ++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index ed673a1633..9a01ba8d15 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -926,7 +926,7 @@ abstract private[data] class EitherTInstances extends EitherTInstances1 { EitherT(F.defer(fa.value)) } - implicit def catsDataParallelForEitherTWithParallelEffect[M[_], E: Semigroup](implicit + private[cats] def catsDataParallelForEitherTWithParallelEffect[M[_], E: Semigroup](implicit P: Parallel[M] ): Parallel.Aux[EitherT[M, E, *], Nested[P.F, Validated[E, *], *]] = new Parallel[EitherT[M, E, *]] { @@ -956,6 +956,37 @@ abstract private[data] class EitherTInstances extends EitherTInstances1 { } } } + + implicit def catsDataParallelForEitherTWithParallelEffect2[M[_], E](implicit + P: Parallel[M] + ): Parallel.Aux[EitherT[M, E, *], Nested[P.F, Either[E, *], *]] = + new Parallel[EitherT[M, E, *]] { + type F[x] = Nested[P.F, Either[E, *], x] + + implicit val monadM: Monad[M] = P.monad + implicit val monadEither: Monad[Either[E, *]] = cats.instances.either.catsStdInstancesForEither + + def applicative: Applicative[Nested[P.F, Either[E, *], *]] = + cats.data.Nested.catsDataApplicativeForNested(P.applicative, implicitly) + + def monad: Monad[EitherT[M, E, *]] = cats.data.EitherT.catsDataMonadErrorForEitherT + + def sequential: Nested[P.F, Either[E, *], *] ~> EitherT[M, E, *] = + new (Nested[P.F, Either[E, *], *] ~> EitherT[M, E, *]) { + def apply[A](nested: Nested[P.F, Either[E, *], A]): EitherT[M, E, A] = { + val mva = P.sequential(nested.value) + EitherT(Functor[M].map(mva)(x => x)) + } + } + + def parallel: EitherT[M, E, *] ~> Nested[P.F, Either[E, *], *] = + new (EitherT[M, E, *] ~> Nested[P.F, Either[E, *], *]) { + def apply[A](eitherT: EitherT[M, E, A]): Nested[P.F, Either[E, *], A] = { + val fea = P.parallel(eitherT.value) + Nested(P.applicative.map(fea)(x => x)) + } + } + } } abstract private[data] class EitherTInstances1 extends EitherTInstances2 { From 957e1292e0b1dda323d9b0f6efdfe47e62e399c0 Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Sun, 7 Feb 2021 23:38:38 -0700 Subject: [PATCH 2/3] Update core/src/main/scala/cats/data/EitherT.scala Co-authored-by: Lars Hupel --- core/src/main/scala/cats/data/EitherT.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 9a01ba8d15..05da3f345f 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -926,7 +926,8 @@ abstract private[data] class EitherTInstances extends EitherTInstances1 { EitherT(F.defer(fa.value)) } - private[cats] def catsDataParallelForEitherTWithParallelEffect[M[_], E: Semigroup](implicit + @deprecated("This implicit provides wrong semantics, see #3776", "2.3.1") + def catsDataParallelForEitherTWithParallelEffect[M[_], E: Semigroup](implicit P: Parallel[M] ): Parallel.Aux[EitherT[M, E, *], Nested[P.F, Validated[E, *], *]] = new Parallel[EitherT[M, E, *]] { From 69857602b6c2320f910725af04d7544f12911fb5 Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Mon, 8 Feb 2021 12:55:06 -0700 Subject: [PATCH 3/3] Added `EitherT.accumulatingParallel` to allow restoration of the non-default semantics --- core/src/main/scala/cats/data/EitherT.scala | 28 ++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 05da3f345f..77de49254f 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -926,9 +926,35 @@ abstract private[data] class EitherTInstances extends EitherTInstances1 { EitherT(F.defer(fa.value)) } - @deprecated("This implicit provides wrong semantics, see #3776", "2.3.1") + @deprecated("This implicit provides inconsistent effect layering semantics; see #3776 for more discussion", "2.4.0") def catsDataParallelForEitherTWithParallelEffect[M[_], E: Semigroup](implicit P: Parallel[M] + ): Parallel.Aux[EitherT[M, E, *], Nested[P.F, Validated[E, *], *]] = + accumulatingParallel[M, E] + + /** + * An alternative [[Parallel]] implementation which merges the semantics of + * the outer Parallel (the F[_] effect) with the effects of the inner + * one (the Either). The inner Parallel has the semantics of [[Validated]], + * while the outer has the semantics of parallel ''evaluation'' (in most cases). + * The default Parallel for [[EitherT]], when the nested F also has a Parallel, + * is to strictly take the semantics of the nested F and to short-circuit any + * lefts (often, errors) in a left-to-right fashion, mirroring the semantics of + * [[Applicative]] on EitherT. This instance is different in that it will not + * ''short-circuit'' but instead accumulate all lefts according to the supplied + * [[Semigroup]], similar to Validated. + * + * {{{ + * implicit val p: Parallel[EitherT[IO, Chain[Error], *]] = EitherT.accumulatingParallel + * + * val a = EitherT(IO(Chain(error1).asLeft[Unit])) + * val b = EitherT(IO(Chain(error2).asLeft[Unit])) + * + * (a, b).parTupled // => EitherT(IO(Chain(error1, error2).asLeft[Unit])) + * }}} + */ + def accumulatingParallel[M[_], E: Semigroup](implicit + P: Parallel[M] ): Parallel.Aux[EitherT[M, E, *], Nested[P.F, Validated[E, *], *]] = new Parallel[EitherT[M, E, *]] { type F[x] = Nested[P.F, Validated[E, *], x]