diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index 1c458ff5c1..60bb4dee1c 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -2,6 +2,7 @@ package cats package data import cats.Bifunctor +import cats.arrow.FunctionK import cats.data.Validated.{Invalid, Valid} import scala.annotation.tailrec @@ -203,6 +204,41 @@ private[data] sealed abstract class IorInstances extends IorInstances0 { new Bifunctor[Ior] { override def bimap[A, B, C, D](fab: A Ior B)(f: A => C, g: B => D): C Ior D = fab.bimap(f, g) } + + implicit def parallelForIor[E] + (implicit E: Semigroup[E]): Parallel[Ior[E, ?], Ior[E, ?]] = new Parallel[Ior[E, ?], Ior[E, ?]] + { + + private[this] val identityK: Ior[E, ?] ~> Ior[E, ?] = FunctionK.id + + def parallel: Ior[E, ?] ~> Ior[E, ?] = identityK + def sequential: Ior[E, ?] ~> Ior[E, ?] = identityK + + val applicative: Applicative[Ior[E, ?]] = new Applicative[Ior[E, ?]] { + def pure[A](a: A): Ior[E, A] = Ior.right(a) + def ap[A, B](ff: Ior[E, A => B])(fa: Ior[E, A]): Ior[E, B] = + fa match { + case Ior.Right(a) => ff match { + case Ior.Right(f) => Ior.Right(f(a)) + case Ior.Both(e1, f) => Ior.Both(e1, f(a)) + case Ior.Left(e1) => Ior.Left(e1) + } + case Ior.Both(e1, a) => ff match { + case Ior.Right(f) => Ior.Both(e1, f(a)) + case Ior.Both(e2, f) => Ior.Both(E.combine(e2, e1), f(a)) + case Ior.Left(e2) => Ior.Left(E.combine(e2, e1)) + } + case Ior.Left(e1) => ff match { + case Ior.Right(f) => Ior.Left(e1) + case Ior.Both(e2, f) => Ior.Left(E.combine(e2, e1)) + case Ior.Left(e2) => Ior.Left(E.combine(e2, e1)) + } + } + } + lazy val monad: Monad[Ior[E, ?]] = Monad[Ior[E, ?]] + } + + } private[data] sealed abstract class IorInstances0 { diff --git a/tests/src/test/scala/cats/tests/ParallelSuite.scala b/tests/src/test/scala/cats/tests/ParallelSuite.scala index d6fe6fd1fe..69861e9aa7 100644 --- a/tests/src/test/scala/cats/tests/ParallelSuite.scala +++ b/tests/src/test/scala/cats/tests/ParallelSuite.scala @@ -1,4 +1,3 @@ - package cats package tests @@ -17,7 +16,7 @@ import scala.collection.immutable.SortedSet class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { - test("ParTraversing Either should accumulate errors") { + test("ParSequence Either should accumulate errors") { forAll { es: List[Either[String, Int]] => val lefts = es.collect { case Left(e) => e @@ -27,6 +26,21 @@ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { } } + test("ParSequence Ior should accumulate errors") { + forAll { es: List[Ior[String, Int]] => + val lefts = es.map(_.left).collect { + case Some(e) => e + }.foldMap(identity) + es.parSequence.left.getOrElse(Monoid[String].empty) should === (lefts) + } + } + + test("ParSequence Ior should sequence values") { + forAll { es: List[Ior[String, Int]] => + es.parSequence.right should === (es.map(_.toOption).sequence) + } + } + test("ParTraverse identity should be equivalent to parSequence") { forAll { es: List[Either[String, Int]] => es.parTraverse(identity) should === (es.parSequence) @@ -156,6 +170,7 @@ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { } checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTests[Either[String, ?], Validated[String, ?]].parallel[Int, String]) + checkAll("Parallel[Ior[String, ?], Ior[String, ?]]", ParallelTests[Ior[String, ?], Ior[String, ?]].parallel[Int, String]) checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?]].parallel[Int, String]) checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?]].parallel[Int, String]) checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]].parallel[Int, String])