From eaf34e631ad516e121d5bbb6dd45830903a8b16f Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 21 Aug 2017 19:36:08 +0200 Subject: [PATCH 01/63] Initial version of Parallel --- core/src/main/scala/cats/Parallel.scala | 60 +++++++++++++++++++ .../src/main/scala/cats/syntax/parallel.scala | 32 ++++++++++ 2 files changed, 92 insertions(+) create mode 100644 core/src/main/scala/cats/Parallel.scala create mode 100644 core/src/main/scala/cats/syntax/parallel.scala diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala new file mode 100644 index 0000000000..da80bacaeb --- /dev/null +++ b/core/src/main/scala/cats/Parallel.scala @@ -0,0 +1,60 @@ +package cats + +trait Parallel[M[_], F[_]] { + def applicative: Applicative[F] + def sequential(implicit M: Monad[M]): F ~> M + def parallel(implicit M: Monad[M]): M ~> F +} + +object Parallel { + def parSequence[T[_]: Traverse, M[_]: Monad, F[_], A] + (tma: T[M[A]])(implicit P: Parallel[M, F]): M[T[A]] = { + implicit val F = P.applicative + val fta: F[T[A]] = Traverse[T].traverse(tma)(P.parallel.apply) + P.sequential.apply(fta) + } + + def parTraverse[T[_]: Traverse, M[_]: Monad, F[_], A, B] + (ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[T[B]] = { + implicit val F = P.applicative + val gtb: F[T[B]] = Traverse[T].traverse(ta)(f andThen P.parallel.apply) + P.sequential.apply(gtb) + } + + def parSequence_[T[_]: Foldable, M[_]: Monad, F[_], A] + (tma: T[M[A]])(implicit P: Parallel[M, F]): M[Unit] = { + implicit val F = P.applicative + val fu: F[Unit] = Foldable[T].traverse_(tma)(P.parallel.apply) + P.sequential.apply(fu) + } + + def parTraverse_[T[_]: Foldable, M[_]: Monad, F[_], A, B] + (ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[Unit] = { + implicit val F = P.applicative + val gtb: F[Unit] = Foldable[T].traverse_(ta)(f andThen P.parallel.apply) + P.sequential.apply(gtb) + } + + def parAp[M[_]: Monad, F[_], A, B](mf: M[A => B]) + (ma: M[A]) + (implicit P: Parallel[M, F]): M[B] = { + implicit val F = P.applicative + val fb = Applicative[F].ap(P.parallel.apply(mf))(P.parallel.apply(ma)) + P.sequential.apply(fb) + } + + def parProduct[M[_]: Monad, F[_], A, B](ma: M[A], mb: M[B]) + (implicit P: Parallel[M, F]): M[(A, B)] = + parAp(Monad[M].map(ma)(a => (b: B) => (a, b)))(mb) + + def parAp2[M[_]: Monad, F[_], A, B, Z](ff: M[(A, B) => Z]) + (ma: M[A], mb: M[B]) + (implicit P: Parallel[M, F]): M[Z] = + Monad[M].map(parProduct(ma, parProduct(mb, ff))) { case (a, (b, f)) => f(a, b) } + + def parMap2[M[_]: Monad, F[_], A, B, C](ma: M[A], mb: M[B]) + (f: (A, B) => C) + (implicit P: Parallel[M, F]): M[C] = { + Monad[M].map(parProduct(ma, mb)) { case (a, b) => f(a, b) } + } +} diff --git a/core/src/main/scala/cats/syntax/parallel.scala b/core/src/main/scala/cats/syntax/parallel.scala new file mode 100644 index 0000000000..7f1b75e4f9 --- /dev/null +++ b/core/src/main/scala/cats/syntax/parallel.scala @@ -0,0 +1,32 @@ +package cats.syntax + +import cats.{Monad, Parallel, Traverse} + +trait ParallelSyntax { + implicit final def catsSyntaxParallelTraverse[T[_]: Traverse, A] + (ta: T[A]): ParallelTraversableOps[T, A] = new ParallelTraversableOps[T, A] { + override def self = ta + + override val typeClassInstance: Traverse[T] = T + } +} + + + + +trait ParallelTraversableOps[T[_], A] { + + val typeClassInstance : cats.Traverse[T] + def self : T[A] + + implicit val T = typeClassInstance + + def parTraverse[M[_]: Monad, F[_], B] + (f: A => M[B])(implicit P: Parallel[M, F]): M[T[B]] = + Parallel.parTraverse(self)(f) + + def parSequence[M[_]: Monad, F[_], B](implicit ev: A <:< M[B], P: Parallel[M, F]): M[T[B]] = + Parallel.parSequence(self.asInstanceOf[T[M[B]]]) + + +} From 21942c590ef3d87a7f4f2d7c31ea98dde9eb6387 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 21 Aug 2017 21:27:35 +0200 Subject: [PATCH 02/63] Add Either/Validated Parallel instance --- .../main/scala/cats/instances/parallel.scala | 19 +++++++++++++++++++ core/src/main/scala/cats/syntax/all.scala | 1 + 2 files changed, 20 insertions(+) create mode 100644 core/src/main/scala/cats/instances/parallel.scala diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala new file mode 100644 index 0000000000..f13ab035a4 --- /dev/null +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -0,0 +1,19 @@ +package cats.instances + +import cats.data.Validated +import cats.kernel.Semigroup +import cats.syntax.either._ +import cats.{Applicative, Monad, Parallel, ~>} + + +trait ParallelInstances { + implicit def parEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { + def applicative: Applicative[Validated[E, ?]] = Validated.catsDataApplicativeErrorForValidated + + def sequential(implicit M: Monad[Either[E, ?]]): Validated[E, ?] ~> Either[E, ?] = + λ[Validated[E, ?] ~> Either[E, ?]](_.toEither) + + def parallel(implicit M: Monad[Either[E, ?]]): Either[E, ?] ~> Validated[E, ?] = + λ[Either[E, ?] ~> Validated[E, ?]](_.toValidated) + } +} diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 2f6d9be450..86dfcb1d3e 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -30,6 +30,7 @@ trait AllSyntax with MonoidSyntax with OptionSyntax with OrderSyntax + with ParallelSyntax with PartialOrderSyntax with ProfunctorSyntax with ReducibleSyntax From b37732b1885392fe0d75bbda1e635441d4425dd1 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 21 Aug 2017 23:26:42 +0200 Subject: [PATCH 03/63] Break up syntax for parallel --- core/src/main/scala/cats/instances/all.scala | 1 + .../src/main/scala/cats/syntax/parallel.scala | 24 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/cats/instances/all.scala b/core/src/main/scala/cats/instances/all.scala index d974ee5d6c..302d11dca9 100644 --- a/core/src/main/scala/cats/instances/all.scala +++ b/core/src/main/scala/cats/instances/all.scala @@ -17,6 +17,7 @@ trait AllInstances with OptionInstances with OrderInstances with OrderingInstances + with ParallelInstances with PartialOrderInstances with PartialOrderingInstances with QueueInstances diff --git a/core/src/main/scala/cats/syntax/parallel.scala b/core/src/main/scala/cats/syntax/parallel.scala index 7f1b75e4f9..c40526cb4b 100644 --- a/core/src/main/scala/cats/syntax/parallel.scala +++ b/core/src/main/scala/cats/syntax/parallel.scala @@ -4,29 +4,27 @@ import cats.{Monad, Parallel, Traverse} trait ParallelSyntax { implicit final def catsSyntaxParallelTraverse[T[_]: Traverse, A] - (ta: T[A]): ParallelTraversableOps[T, A] = new ParallelTraversableOps[T, A] { - override def self = ta + (ta: T[A]): ParallelTraversableOps[T, A] = new ParallelTraversableOps[T, A](ta) - override val typeClassInstance: Traverse[T] = T - } + implicit final def catsSyntaxParallelSequence[T[_]: Traverse, M[_]: Monad, A] + (tma: T[M[A]]): ParallelSequenceOps[T, M, A] = new ParallelSequenceOps[T, M, A](tma) } -trait ParallelTraversableOps[T[_], A] { +final class ParallelTraversableOps[T[_], A](val ta: T[A]) extends AnyVal { - val typeClassInstance : cats.Traverse[T] - def self : T[A] - - implicit val T = typeClassInstance def parTraverse[M[_]: Monad, F[_], B] - (f: A => M[B])(implicit P: Parallel[M, F]): M[T[B]] = - Parallel.parTraverse(self)(f) + (f: A => M[B])(implicit T: Traverse[T], P: Parallel[M, F]): M[T[B]] = + Parallel.parTraverse(ta)(f) - def parSequence[M[_]: Monad, F[_], B](implicit ev: A <:< M[B], P: Parallel[M, F]): M[T[B]] = - Parallel.parSequence(self.asInstanceOf[T[M[B]]]) +} +final class ParallelSequenceOps[T[_], M[_], A](val tma: T[M[A]]) extends AnyVal { + def parSequence[F[_]] + (implicit M: Monad[M], T: Traverse[T], P: Parallel[M, F]): M[T[A]] = + Parallel.parSequence(tma) } From 664988ab2dd2cc087ab3dcc31a5944c9d7700b0f Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 21 Aug 2017 23:42:27 +0200 Subject: [PATCH 04/63] Add Parallel syntax tests --- tests/src/test/scala/cats/tests/SyntaxTests.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index c4e17a28e4..32614bff89 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -152,6 +152,17 @@ object SyntaxTests extends AllInstances with AllSyntax { val gunit: G[F[A]] = fga.nonEmptySequence } + + + def testParallel[M[_]: Monad, F[_], T[_]: Traverse, A, B](implicit P: Parallel[M, F]): Unit = { + val ta = mock[T[A]] + val f = mock[A => M[B]] + val mtb = ta.parTraverse(f) + + val tma = mock[T[M[A]]] + val mta = tma.parSequence + } + def testReducible[F[_]: Reducible, G[_]: Apply: SemigroupK, A: Semigroup, B, Z]: Unit = { val fa = mock[F[A]] val f1 = mock[(A, A) => A] From e7f6f68686f72de4c5476fa1743ec61787590f21 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 22 Aug 2017 13:16:58 +0200 Subject: [PATCH 05/63] Add Tuple syntax for Parallel --- core/src/main/scala/cats/Parallel.scala | 8 +-- .../src/main/scala/cats/syntax/parallel.scala | 5 +- project/Boilerplate.scala | 66 ++++++++++++++++++- .../test/scala/cats/tests/SyntaxTests.scala | 11 ++++ 4 files changed, 78 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index da80bacaeb..13d47763bb 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -6,7 +6,7 @@ trait Parallel[M[_], F[_]] { def parallel(implicit M: Monad[M]): M ~> F } -object Parallel { +object Parallel extends ParallelArityFunctions { def parSequence[T[_]: Traverse, M[_]: Monad, F[_], A] (tma: T[M[A]])(implicit P: Parallel[M, F]): M[T[A]] = { implicit val F = P.applicative @@ -51,10 +51,4 @@ object Parallel { (ma: M[A], mb: M[B]) (implicit P: Parallel[M, F]): M[Z] = Monad[M].map(parProduct(ma, parProduct(mb, ff))) { case (a, (b, f)) => f(a, b) } - - def parMap2[M[_]: Monad, F[_], A, B, C](ma: M[A], mb: M[B]) - (f: (A, B) => C) - (implicit P: Parallel[M, F]): M[C] = { - Monad[M].map(parProduct(ma, mb)) { case (a, b) => f(a, b) } - } } diff --git a/core/src/main/scala/cats/syntax/parallel.scala b/core/src/main/scala/cats/syntax/parallel.scala index c40526cb4b..e5bff9bf6c 100644 --- a/core/src/main/scala/cats/syntax/parallel.scala +++ b/core/src/main/scala/cats/syntax/parallel.scala @@ -2,7 +2,7 @@ package cats.syntax import cats.{Monad, Parallel, Traverse} -trait ParallelSyntax { +trait ParallelSyntax extends TupleParallelSyntax { implicit final def catsSyntaxParallelTraverse[T[_]: Traverse, A] (ta: T[A]): ParallelTraversableOps[T, A] = new ParallelTraversableOps[T, A](ta) @@ -11,11 +11,8 @@ trait ParallelSyntax { } - - final class ParallelTraversableOps[T[_], A](val ta: T[A]) extends AnyVal { - def parTraverse[M[_]: Monad, F[_], B] (f: A => M[B])(implicit T: Traverse[T], P: Parallel[M, F]): M[T[B]] = Parallel.parTraverse(ta)(f) diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index 86b541893f..66510cfe8b 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -28,7 +28,9 @@ object Boilerplate { GenCartesianBuilders, GenCartesianArityFunctions, GenApplyArityFunctions, - GenTupleCartesianSyntax + GenTupleCartesianSyntax, + GenParallelArityFunctions, + GenTupleParallelSyntax ) val header = "// auto-generated boilerplate" // TODO: put something meaningful here? @@ -192,6 +194,30 @@ object Boilerplate { } } + object GenParallelArityFunctions extends Template { + def filename(root: File) = root / "cats" / "ParallelArityFunctions.scala" + override def range = 2 to maxArity + def content(tv: TemplateVals) = { + import tv._ + + val tpes = synTypes map { tpe => s"M[$tpe]" } + val fargs = (0 until arity) map { "m" + _ } + val fparams = (fargs zip tpes) map { case (v,t) => s"$v:$t"} mkString ", " + val fargsS = fargs mkString ", " + + val nestedProducts = (0 until (arity - 2)).foldRight(s"Parallel.parProduct(m${arity - 2}, m${arity - 1})")((i, acc) => s"Parallel.parProduct(m$i, $acc)") + val `nested (a..n)` = (0 until (arity - 2)).foldRight(s"(a${arity - 2}, a${arity - 1})")((i, acc) => s"(a$i, $acc)") + + block""" + |package cats + |trait ParallelArityFunctions { + - def parMap$arity[M[_]: Monad, F[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit p: Parallel[M, F]): M[Z] = + - Monad[M].map($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } + |} + """ + } + } + object GenCartesianArityFunctions extends Template { def filename(root: File) = root / "cats" / "CartesianArityFunctions.scala" override def range = 2 to maxArity @@ -222,6 +248,44 @@ object Boilerplate { } } + object GenTupleParallelSyntax extends Template { + def filename(root: File) = root / "cats" / "syntax" / "TupleParallelSyntax.scala" + + def content(tv: TemplateVals) = { + import tv._ + + val tpes = synTypes map { tpe => s"M[$tpe]" } + val tpesString = tpes mkString ", " + + val tuple = s"Tuple$arity[$tpesString]" + val tupleTpe = s"t$arity: $tuple" + val tupleArgs = (1 to arity) map { case n => s"t$arity._$n" } mkString ", " + + val n = if (arity == 1) { "" } else { arity.toString } + + val parMap = + if (arity == 1) s"def parMap[F[_], Z](f: (${`A..N`}) => Z)(implicit p: Parallel[M, F]): M[Z] = Monad[M].map($tupleArgs)(f)" + else s"def parMapN[F[_], Z](f: (${`A..N`}) => Z)(implicit p: Parallel[M, F]): M[Z] = Parallel.parMap$arity($tupleArgs)(f)" + + + block""" + |package cats + |package syntax + | + |import cats.{Monad, Parallel} + | + |trait TupleParallelSyntax { + - implicit def catsSyntaxTuple${arity}Parallel[M[_]: Monad, ${`A..N`}]($tupleTpe): Tuple${arity}ParallelOps[M, ${`A..N`}] = new Tuple${arity}ParallelOps(t$arity) + |} + | + -private[syntax] final class Tuple${arity}ParallelOps[M[_]: Monad, ${`A..N`}]($tupleTpe) { + - $parMap + -} + | + """ + } + } + object GenTupleCartesianSyntax extends Template { def filename(root: File) = root / "cats" / "syntax" / "TupleCartesianSyntax.scala" diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index 32614bff89..2511e2ab6e 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -163,6 +163,17 @@ object SyntaxTests extends AllInstances with AllSyntax { val mta = tma.parSequence } + def testParallelTuple[M[_]: Monad, F[_], A, B, C, Z](implicit P: Parallel[M, F]) = { + val tfabc = mock[(M[A], M[B], M[C])] + val fa = mock[M[A]] + val fb = mock[M[B]] + val fc = mock[M[C]] + val f = mock[(A, B, C) => Z] + + tfabc parMapN f + (fa, fb, fc) parMapN f + } + def testReducible[F[_]: Reducible, G[_]: Apply: SemigroupK, A: Semigroup, B, Z]: Unit = { val fa = mock[F[A]] val f1 = mock[(A, A) => A] From f37500a2a0924196bd33c741ec795047037462a4 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 22 Aug 2017 18:48:46 +0200 Subject: [PATCH 06/63] Add ParallelTests --- .../main/scala/cats/laws/ParallelLaws.scala | 19 ++++++++++++++++ .../cats/laws/discipline/ParallelTests.scala | 22 +++++++++++++++++++ .../test/scala/cats/tests/ParallelTests.scala | 11 ++++++++++ 3 files changed, 52 insertions(+) create mode 100644 laws/src/main/scala/cats/laws/ParallelLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/ParallelTests.scala create mode 100644 tests/src/test/scala/cats/tests/ParallelTests.scala diff --git a/laws/src/main/scala/cats/laws/ParallelLaws.scala b/laws/src/main/scala/cats/laws/ParallelLaws.scala new file mode 100644 index 0000000000..ad79ac05e6 --- /dev/null +++ b/laws/src/main/scala/cats/laws/ParallelLaws.scala @@ -0,0 +1,19 @@ +package cats +package laws + + +/** + * Laws that must be obeyed by any `cats.Parallel`. + */ +trait ParallelLaws[M[_], F[_]] { + def monadM: Monad[M] + def P: Parallel[M, F] + + def parallelRoundTrip[A](ma: M[A]): IsEq[M[A]] = + P.sequential(monadM)(P.parallel(monadM)(ma)) <-> ma +} + +object ParallelLaws { + def apply[M[_]: Monad, F[_]](implicit ev: Parallel[M, F]): ParallelLaws[M, F] = + new ParallelLaws[M, F] { def P: Parallel[M, F] = ev; def monadM: Monad[M] = Monad[M] } +} diff --git a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala new file mode 100644 index 0000000000..d2069c97c0 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala @@ -0,0 +1,22 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop.forAll +import org.typelevel.discipline.Laws + +trait ParallelTests[M[_], F[_]] extends Laws { + def laws: ParallelLaws[M, F] + + def parallel[A](implicit ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]]): RuleSet = new DefaultRuleSet( + "parallel", + None, + "parallel round trip" -> forAll((ma: M[A]) => laws.parallelRoundTrip(ma)) + ) +} + +object ParallelTests { + def apply[M[_]: Monad, F[_]](implicit ev: Parallel[M, F]): ParallelTests[M, F] = + new ParallelTests[M, F] { val laws: ParallelLaws[M, F] = ParallelLaws[M, F] } +} diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala new file mode 100644 index 0000000000..af1b712172 --- /dev/null +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -0,0 +1,11 @@ +package cats + + +import cats.data.Validated +import cats.tests.CatsSuite +import cats.laws.discipline.{ParallelTests => ParallelTypeclassTests} + +class ParallelTests extends CatsSuite { + + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?]].parallel) +} From 447d929bbcdd49c04d413a49475068894efeacde Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 23 Aug 2017 08:30:39 +0200 Subject: [PATCH 07/63] Fix Parallel law --- core/src/main/scala/cats/instances/package.scala | 1 + core/src/main/scala/cats/syntax/package.scala | 1 + .../scala/cats/laws/discipline/ParallelTests.scala | 8 ++++---- .../src/test/scala/cats/tests/ParallelTests.scala | 14 +++++++++++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/instances/package.scala b/core/src/main/scala/cats/instances/package.scala index 85dadb037c..5ce8c11709 100644 --- a/core/src/main/scala/cats/instances/package.scala +++ b/core/src/main/scala/cats/instances/package.scala @@ -24,6 +24,7 @@ package object instances { object option extends OptionInstances object order extends OrderInstances object ordering extends OrderingInstances + object parallel extends ParallelInstances object partialOrder extends PartialOrderInstances object partialOrdering extends PartialOrderingInstances object queue extends QueueInstances diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index f8c035de2a..eb92a5f96b 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -30,6 +30,7 @@ package object syntax { object monoid extends MonoidSyntax object option extends OptionSyntax object order extends OrderSyntax + object parallel extends ParallelSyntax object partialOrder extends PartialOrderSyntax object profunctor extends ProfunctorSyntax object reducible extends ReducibleSyntax diff --git a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala index d2069c97c0..8fc2af6930 100644 --- a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala @@ -6,10 +6,10 @@ import org.scalacheck.Arbitrary import org.scalacheck.Prop.forAll import org.typelevel.discipline.Laws -trait ParallelTests[M[_], F[_]] extends Laws { +trait ParallelTests[M[_], F[_], A] extends Laws { def laws: ParallelLaws[M, F] - def parallel[A](implicit ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]]): RuleSet = new DefaultRuleSet( + def parallel(implicit ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]]): RuleSet = new DefaultRuleSet( "parallel", None, "parallel round trip" -> forAll((ma: M[A]) => laws.parallelRoundTrip(ma)) @@ -17,6 +17,6 @@ trait ParallelTests[M[_], F[_]] extends Laws { } object ParallelTests { - def apply[M[_]: Monad, F[_]](implicit ev: Parallel[M, F]): ParallelTests[M, F] = - new ParallelTests[M, F] { val laws: ParallelLaws[M, F] = ParallelLaws[M, F] } + def apply[M[_]: Monad, F[_], A](implicit ev: Parallel[M, F]): ParallelTests[M, F, A] = + new ParallelTests[M, F, A] { val laws: ParallelLaws[M, F] = ParallelLaws[M, F] } } diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index af1b712172..77dea3812b 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -7,5 +7,17 @@ import cats.laws.discipline.{ParallelTests => ParallelTypeclassTests} class ParallelTests extends CatsSuite { - checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?]].parallel) + + test("ParTraversing Either should accumulate errors") { + forAll { es: List[Either[String, Int]] => + val lefts = es.collect { + case Left(e) => e + }.foldMap(identity) + + (es.parSequence.fold(identity, i => Monoid[String].empty)) should === (lefts) + } + } + + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) + } From 425955484c0201850e118e7ab96beebda7ce3b33 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 23 Aug 2017 10:24:30 +0200 Subject: [PATCH 08/63] Add more tests --- .../test/scala/cats/tests/ParallelTests.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 77dea3812b..b076370f02 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -18,6 +18,25 @@ class ParallelTests extends CatsSuite { } } + test("ParTraverse identity should be equivalent to parSequence") { + forAll { es: List[Either[String, Int]] => + (es.parTraverse(identity)) should === (es.parSequence) + } + } + + test("ParTraverse_ identity should be equivalent to parSequence_") { + forAll { es: Set[Either[String, Int]] => + (Parallel.parTraverse_(es)(identity)) should === (Parallel.parSequence_(es)) + } + } + + test("parAp2 accumulates errors in order") { + val plus = (_: Int) + (_: Int) + val rightPlus: Either[String, (Int, Int) => Int] = Right(plus) + Parallel.parAp2(rightPlus)("Hello".asLeft, "World".asLeft) should === (Left("HelloWorld")) + } + + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) } From 6c5efc93a0b87b570980d000fe5b14b2ceaed225 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 23 Aug 2017 10:57:31 +0200 Subject: [PATCH 09/63] Add Parallel Kleisli instance --- core/src/main/scala/cats/data/Kleisli.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 501c3101c4..093a51789c 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -128,6 +128,18 @@ private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 { implicit def catsDataMonadForKleisli[F[_], A](implicit M: Monad[F]): Monad[Kleisli[F, A, ?]] = new KleisliMonad[F, A] { def F: Monad[F] = M } + + implicit def catsDataParallelForKleisli[F[_], M[_]: Monad, A] + (implicit P: Parallel[M, F]): Parallel[Kleisli[M, A, ?], Kleisli[F, A, ?]] = new Parallel[Kleisli[M, A, ?], Kleisli[F, A, ?]]{ + implicit val appF = P.applicative + def applicative: Applicative[Kleisli[F, A, ?]] = catsDataApplicativeForKleisli + + def sequential(implicit M: Monad[Kleisli[M, A, ?]]): Kleisli[F, A, ?] ~> Kleisli[M, A, ?] = + λ[Kleisli[F, A, ?] ~> Kleisli[M, A, ?]](_.transform(P.sequential)) + + def parallel(implicit M: Monad[Kleisli[M, A, ?]]): Kleisli[M, A, ?] ~> Kleisli[F, A, ?] = + λ[Kleisli[M, A, ?] ~> Kleisli[F, A, ?]](_.transform(P.parallel)) + } } private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3 { From d7ba3389fb5e826663e64b5eff211f1991eebeb8 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 23 Aug 2017 11:30:16 +0200 Subject: [PATCH 10/63] Add instances for OptionT and EitherT to nested --- .../main/scala/cats/instances/parallel.scala | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index f13ab035a4..2a8aae6cac 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -1,6 +1,6 @@ package cats.instances -import cats.data.Validated +import cats.data.{EitherT, Nested, OptionT, Validated} import cats.kernel.Semigroup import cats.syntax.either._ import cats.{Applicative, Monad, Parallel, ~>} @@ -16,4 +16,34 @@ trait ParallelInstances { def parallel(implicit M: Monad[Either[E, ?]]): Either[E, ?] ~> Validated[E, ?] = λ[Either[E, ?] ~> Validated[E, ?]](_.toValidated) } + + implicit def catsDataParallelForOptionT[F[_], M[_]: Monad] + (implicit P: Parallel[M, F]): Parallel[OptionT[M, ?], Nested[F, Option, ?]] = new Parallel[OptionT[M, ?], Nested[F, Option, ?]] { + implicit val appF: Applicative[F] = P.applicative + implicit val appOption: Applicative[Option] = cats.instances.option.catsStdInstancesForOption + + def applicative: Applicative[Nested[F, Option, ?]] = cats.data.Nested.catsDataApplicativeForNested[F, Option] + + def sequential(implicit M: Monad[OptionT[M, ?]]): Nested[F, Option, ?] ~> OptionT[M, ?] = + λ[Nested[F, Option, ?] ~> OptionT[M, ?]](nested => OptionT(P.sequential.apply(nested.value))) + + def parallel(implicit M: Monad[OptionT[M, ?]]): OptionT[M, ?]~> Nested[F, Option, ?] = + λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel.apply(optT.value))) + } + + implicit def catsDataParallelForEitherT[F[_], M[_]: Monad, E] + (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Either[E, ?], ?]] = + new Parallel[EitherT[M, E, ?], Nested[F, Either[E, ?], ?]] { + + implicit val appF: Applicative[F] = P.applicative + implicit val appOption: Applicative[Either[E, ?]] = cats.instances.either.catsStdInstancesForEither + + def applicative: Applicative[Nested[F, Either[E, ?], ?]] = cats.data.Nested.catsDataApplicativeForNested[F, Either[E, ?]] + + def sequential(implicit M: Monad[EitherT[M, E, ?]]): Nested[F, Either[E, ?], ?] ~> EitherT[M, E, ?] = + λ[Nested[F, Either[E, ?], ?] ~> EitherT[M, E, ?]](nested => EitherT(P.sequential.apply(nested.value))) + + def parallel(implicit M: Monad[EitherT[M, E, ?]]): EitherT[M, E, ?]~> Nested[F, Either[E, ?], ?] = + λ[EitherT[M, E, ?] ~> Nested[F, Either[E, ?], ?]](eitherT => Nested(P.parallel.apply(eitherT.value))) + } } From a7bea82ac577e719ba4ced923387003ff1766e23 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 23 Aug 2017 12:10:55 +0200 Subject: [PATCH 11/63] Add law tests for parallel OptionT and EitherT --- tests/src/test/scala/cats/tests/ParallelTests.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index b076370f02..e40c1928e0 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -1,9 +1,10 @@ package cats -import cats.data.Validated +import cats.data.{EitherT, Nested, OptionT, Validated} import cats.tests.CatsSuite import cats.laws.discipline.{ParallelTests => ParallelTypeclassTests} +import org.scalacheck.Arbitrary class ParallelTests extends CatsSuite { @@ -36,7 +37,13 @@ class ParallelTests extends CatsSuite { Parallel.parAp2(rightPlus)("Hello".asLeft, "World".asLeft) should === (Left("HelloWorld")) } - checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) + { + implicit val arbO: Arbitrary[OptionT[Either[String, ?], Int]] = cats.laws.discipline.arbitrary.catsLawsArbitraryForOptionT + implicit val arbE: Arbitrary[EitherT[Either[String, ?], String, Int]] = cats.laws.discipline.arbitrary.catsLawsArbitraryForEitherT + + checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) + checkAll("Parallel[EitherT[M, E, ?], Nested[F, Either[E, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Either[String, ?], ?], Int].parallel) + } } From 1556909722bf8fee655509be758ce033be2b1bf8 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 23 Aug 2017 12:40:11 +0200 Subject: [PATCH 12/63] Make EitherT instance forward to Validated --- .../main/scala/cats/instances/parallel.scala | 30 +++++++++++-------- .../test/scala/cats/tests/ParallelTests.scala | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 2a8aae6cac..94ccf57923 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -3,11 +3,11 @@ package cats.instances import cats.data.{EitherT, Nested, OptionT, Validated} import cats.kernel.Semigroup import cats.syntax.either._ -import cats.{Applicative, Monad, Parallel, ~>} +import cats.{Applicative, Functor, Monad, Parallel, ~>} trait ParallelInstances { - implicit def parEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { + implicit def catsParallelForEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { def applicative: Applicative[Validated[E, ?]] = Validated.catsDataApplicativeErrorForValidated def sequential(implicit M: Monad[Either[E, ?]]): Validated[E, ?] ~> Either[E, ?] = @@ -17,7 +17,7 @@ trait ParallelInstances { λ[Either[E, ?] ~> Validated[E, ?]](_.toValidated) } - implicit def catsDataParallelForOptionT[F[_], M[_]: Monad] + implicit def catsParallelForOptionTNestedOption[F[_], M[_]: Monad] (implicit P: Parallel[M, F]): Parallel[OptionT[M, ?], Nested[F, Option, ?]] = new Parallel[OptionT[M, ?], Nested[F, Option, ?]] { implicit val appF: Applicative[F] = P.applicative implicit val appOption: Applicative[Option] = cats.instances.option.catsStdInstancesForOption @@ -31,19 +31,25 @@ trait ParallelInstances { λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel.apply(optT.value))) } - implicit def catsDataParallelForEitherT[F[_], M[_]: Monad, E] - (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Either[E, ?], ?]] = - new Parallel[EitherT[M, E, ?], Nested[F, Either[E, ?], ?]] { + implicit def catsParallelForEitherTNestedValidated[F[_], M[_]: Monad, E: Semigroup] + (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] = + new Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] { implicit val appF: Applicative[F] = P.applicative - implicit val appOption: Applicative[Either[E, ?]] = cats.instances.either.catsStdInstancesForEither + implicit val appValidated: Applicative[Validated[E, ?]] = Validated.catsDataApplicativeErrorForValidated - def applicative: Applicative[Nested[F, Either[E, ?], ?]] = cats.data.Nested.catsDataApplicativeForNested[F, Either[E, ?]] + def applicative: Applicative[Nested[F, Validated[E, ?], ?]] = cats.data.Nested.catsDataApplicativeForNested[F, Validated[E, ?]] - def sequential(implicit M: Monad[EitherT[M, E, ?]]): Nested[F, Either[E, ?], ?] ~> EitherT[M, E, ?] = - λ[Nested[F, Either[E, ?], ?] ~> EitherT[M, E, ?]](nested => EitherT(P.sequential.apply(nested.value))) + def sequential(implicit M: Monad[EitherT[M, E, ?]]): Nested[F, Validated[E, ?], ?] ~> EitherT[M, E, ?] = + λ[Nested[F, Validated[E, ?], ?] ~> EitherT[M, E, ?]] { nested => + val mva = P.sequential.apply(nested.value) + EitherT(Functor[M].map(mva)(_.toEither)) + } - def parallel(implicit M: Monad[EitherT[M, E, ?]]): EitherT[M, E, ?]~> Nested[F, Either[E, ?], ?] = - λ[EitherT[M, E, ?] ~> Nested[F, Either[E, ?], ?]](eitherT => Nested(P.parallel.apply(eitherT.value))) + def parallel(implicit M: Monad[EitherT[M, E, ?]]): EitherT[M, E, ?]~> Nested[F, Validated[E, ?], ?] = + λ[EitherT[M, E, ?] ~> Nested[F, Validated[E, ?], ?]] { eitherT => + val fea = P.parallel.apply(eitherT.value) + Nested(Functor[F].map(fea)(_.toValidated)) + } } } diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index e40c1928e0..9ff4d62930 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -44,6 +44,6 @@ class ParallelTests extends CatsSuite { implicit val arbE: Arbitrary[EitherT[Either[String, ?], String, Int]] = cats.laws.discipline.arbitrary.catsLawsArbitraryForEitherT checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) - checkAll("Parallel[EitherT[M, E, ?], Nested[F, Either[E, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Either[String, ?], ?], Int].parallel) + checkAll("Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) } } From b3470dbf70e7f8cb448fdb045b01ceeb902b361e Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 23 Aug 2017 18:45:28 +0200 Subject: [PATCH 13/63] Add Kleisli lawTest --- tests/src/test/scala/cats/tests/ParallelTests.scala | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 9ff4d62930..57a4533975 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -1,9 +1,11 @@ package cats -import cats.data.{EitherT, Nested, OptionT, Validated} +import cats.data._ import cats.tests.CatsSuite import cats.laws.discipline.{ParallelTests => ParallelTypeclassTests} +import cats.laws.discipline.eq._ +import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary class ParallelTests extends CatsSuite { @@ -38,12 +40,13 @@ class ParallelTests extends CatsSuite { } checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) + checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) + checkAll("Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) { - implicit val arbO: Arbitrary[OptionT[Either[String, ?], Int]] = cats.laws.discipline.arbitrary.catsLawsArbitraryForOptionT - implicit val arbE: Arbitrary[EitherT[Either[String, ?], String, Int]] = cats.laws.discipline.arbitrary.catsLawsArbitraryForEitherT + implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = + Eq.by[Kleisli[F, A, B], A => F[B]](_.run) - checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) - checkAll("Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) + checkAll("Parallel[KlesliT[Id, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?], Int].parallel) } } From b458b3d402f3e88f5921fc5387c2b712afa77ebe Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 24 Aug 2017 01:55:05 +0200 Subject: [PATCH 14/63] Add WriterT instance --- core/src/main/scala/cats/data/WriterT.scala | 12 ++++++++++++ tests/src/test/scala/cats/tests/ParallelTests.scala | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index e6d48ff1a3..2f811ac42e 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -81,6 +81,18 @@ private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 implicit val L0: Monoid[L] = L } + implicit def catsDataParallelForWriterT[F[_], M[_]: Monad, L: Monoid] + (implicit P: Parallel[M, F]): Parallel[WriterT[M, L, ?], WriterT[F, L, ?]] = new Parallel[WriterT[M, L, ?], WriterT[F, L, ?]]{ + implicit val appF = P.applicative + def applicative: Applicative[WriterT[F, L, ?]] = catsDataApplicativeForWriterT + + def sequential(implicit M: Monad[WriterT[M, L, ?]]): WriterT[F, L, ?] ~> WriterT[M, L, ?] = + λ[WriterT[F, L, ?] ~> WriterT[M, L, ?]](wfl => WriterT(P.sequential.apply(wfl.run))) + + def parallel(implicit M: Monad[WriterT[M, L, ?]]): WriterT[M, L, ?] ~> WriterT[F, L, ?] = + λ[WriterT[M, L, ?] ~> WriterT[F, L, ?]](wml => WriterT(P.parallel.apply(wml.run))) + } + implicit def catsDataEqForWriterTId[L: Eq, V: Eq]: Eq[WriterT[Id, L, V]] = catsDataEqForWriterT[Id, L, V] diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 57a4533975..b9be4b25e3 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -41,12 +41,13 @@ class ParallelTests extends CatsSuite { checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) - checkAll("Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) + checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) + checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) - checkAll("Parallel[KlesliT[Id, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?], Int].parallel) + checkAll("Parallel[KlesliT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?], Int].parallel) } } From 2815a5bb485ccde45db9154452472a5cdcde5110 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 24 Aug 2017 12:26:07 +0200 Subject: [PATCH 15/63] Add more tests --- .../test/scala/cats/tests/ParallelTests.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index b9be4b25e3..5108139326 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -39,6 +39,23 @@ class ParallelTests extends CatsSuite { Parallel.parAp2(rightPlus)("Hello".asLeft, "World".asLeft) should === (Left("HelloWorld")) } + test("Kleisli with Either should accumulate errors") { + val k1: Kleisli[Either[String, ?], String, Int] = Kleisli(s => Right(s.length)) + val k2: Kleisli[Either[String, ?], String, Int] = Kleisli(s => Left("Boo")) + val k3: Kleisli[Either[String, ?], String, Int] = Kleisli(s => Left("Nope")) + + (List(k1,k2,k3).parSequence.run("Hello")) should === (Left("BooNope")) + + } + + test("WriterT with Either should accumulate errors") { + val w1: WriterT[Either[String, ?], String, Int] = WriterT.lift(Left("Too ")) + val w2: WriterT[Either[String, ?], String, Int] = WriterT.lift(Left("bad.")) + + ((w1,w2).parMapN(_ + _).value) should === (Left("Too bad.")) + + } + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) From 7844788cc67c2d190b9c523a4482c60cd2073071 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 24 Aug 2017 15:40:20 +0200 Subject: [PATCH 16/63] Add scaladoc --- core/src/main/scala/cats/Parallel.scala | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 13d47763bb..82441c48d5 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -1,12 +1,32 @@ package cats +/** + * Some types that form a Monad, are also capable of forming an Applicative that supports parallel composition. + * The Parallel type class allows us to represent this relationship. + */ trait Parallel[M[_], F[_]] { + /** + * The applicative instance for F[_] + */ def applicative: Applicative[F] + + /** + * Natural Transformation from the sequential Monad M[_] to the parallel Applicative F[_]. + */ def sequential(implicit M: Monad[M]): F ~> M + + /** + * Natural Transformation from the parallel Applicative F[_] to the sequential Monad M[_]. + */ def parallel(implicit M: Monad[M]): M ~> F } object Parallel extends ParallelArityFunctions { + + /** + * Like `Traverse[A].sequence`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ def parSequence[T[_]: Traverse, M[_]: Monad, F[_], A] (tma: T[M[A]])(implicit P: Parallel[M, F]): M[T[A]] = { implicit val F = P.applicative @@ -14,6 +34,10 @@ object Parallel extends ParallelArityFunctions { P.sequential.apply(fta) } + /** + * Like `Traverse[A].traverse`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ def parTraverse[T[_]: Traverse, M[_]: Monad, F[_], A, B] (ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[T[B]] = { implicit val F = P.applicative @@ -21,6 +45,10 @@ object Parallel extends ParallelArityFunctions { P.sequential.apply(gtb) } + /** + * Like `Foldable[A].sequence_`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ def parSequence_[T[_]: Foldable, M[_]: Monad, F[_], A] (tma: T[M[A]])(implicit P: Parallel[M, F]): M[Unit] = { implicit val F = P.applicative @@ -28,6 +56,10 @@ object Parallel extends ParallelArityFunctions { P.sequential.apply(fu) } + /** + * Like `Foldable[A].traverse_`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ def parTraverse_[T[_]: Foldable, M[_]: Monad, F[_], A, B] (ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[Unit] = { implicit val F = P.applicative @@ -35,6 +67,10 @@ object Parallel extends ParallelArityFunctions { P.sequential.apply(gtb) } + /** + * Like `Applicative[F].ap`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ def parAp[M[_]: Monad, F[_], A, B](mf: M[A => B]) (ma: M[A]) (implicit P: Parallel[M, F]): M[B] = { @@ -43,10 +79,18 @@ object Parallel extends ParallelArityFunctions { P.sequential.apply(fb) } + /** + * Like `Applicative[F].product`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ def parProduct[M[_]: Monad, F[_], A, B](ma: M[A], mb: M[B]) (implicit P: Parallel[M, F]): M[(A, B)] = parAp(Monad[M].map(ma)(a => (b: B) => (a, b)))(mb) + /** + * Like `Applicative[F].ap2`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ def parAp2[M[_]: Monad, F[_], A, B, Z](ff: M[(A, B) => Z]) (ma: M[A], mb: M[B]) (implicit P: Parallel[M, F]): M[Z] = From 378549f5a12bf9028643ba4765c83ae2c795f691 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 26 Aug 2017 23:48:19 +0200 Subject: [PATCH 17/63] Add ApplicativeError instance for MonadError and Parallel --- .../main/scala/cats/instances/parallel.scala | 18 +++++++++++++++++- .../test/scala/cats/tests/ParallelTests.scala | 4 +++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 94ccf57923..cfe2f68990 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -3,7 +3,7 @@ package cats.instances import cats.data.{EitherT, Nested, OptionT, Validated} import cats.kernel.Semigroup import cats.syntax.either._ -import cats.{Applicative, Functor, Monad, Parallel, ~>} +import cats.{Applicative, ApplicativeError, Functor, Monad, MonadError, Parallel, ~>} trait ParallelInstances { @@ -17,6 +17,22 @@ trait ParallelInstances { λ[Either[E, ?] ~> Validated[E, ?]](_.toValidated) } + implicit def catsApplicativeErrorForParallelMonadError[F[_], M[_], E] + (implicit P: Parallel[M, F], E: MonadError[M, E]): ApplicativeError[F, E] = new ApplicativeError[F, E] { + + def raiseError[A](e: E): F[A] = + P.parallel.apply(MonadError[M, E].raiseError(e)) + + def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { + val ma = MonadError[M, E].handleErrorWith(P.sequential.apply(fa))(f andThen P.sequential.apply) + P.parallel.apply(ma) + } + + def pure[A](x: A): F[A] = P.applicative.pure(x) + + def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = P.applicative.ap(ff)(fa) + } + implicit def catsParallelForOptionTNestedOption[F[_], M[_]: Monad] (implicit P: Parallel[M, F]): Parallel[OptionT[M, ?], Nested[F, Option, ?]] = new Parallel[OptionT[M, ?], Nested[F, Option, ?]] { implicit val appF: Applicative[F] = P.applicative diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 5108139326..30c69b04c7 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -3,7 +3,7 @@ package cats import cats.data._ import cats.tests.CatsSuite -import cats.laws.discipline.{ParallelTests => ParallelTypeclassTests} +import cats.laws.discipline.{ApplicativeErrorTests, ParallelTests => ParallelTypeclassTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -67,4 +67,6 @@ class ParallelTests extends CatsSuite { checkAll("Parallel[KlesliT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?], Int].parallel) } + + checkAll("ApplicativeError[Either[String, Int]]", ApplicativeErrorTests[Either[String, ?], String].applicativeError[Int, Int, Int]) } From 1490fe411f88becc528e9ba06e6a599ba381fd8e Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 27 Aug 2017 00:39:31 +0200 Subject: [PATCH 18/63] Add Test that actually hits the implicated ApplicativeError instance --- core/src/main/scala/cats/Parallel.scala | 2 ++ .../test/scala/cats/tests/ParallelTests.scala | 21 +++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 82441c48d5..140176964c 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -23,6 +23,8 @@ trait Parallel[M[_], F[_]] { object Parallel extends ParallelArityFunctions { + def apply[M[_], F[_]](implicit P: Parallel[M, F]): Parallel[M, F] = P + /** * Like `Traverse[A].sequence`, but uses the applicative instance * corresponding to the Parallel instance instead. diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 30c69b04c7..d79489dce1 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -3,12 +3,14 @@ package cats import cats.data._ import cats.tests.CatsSuite +import org.scalatest.FunSuite import cats.laws.discipline.{ApplicativeErrorTests, ParallelTests => ParallelTypeclassTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary +import org.typelevel.discipline.scalatest.Discipline -class ParallelTests extends CatsSuite { +class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { test("ParTraversing Either should accumulate errors") { @@ -68,5 +70,20 @@ class ParallelTests extends CatsSuite { checkAll("Parallel[KlesliT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?], Int].parallel) } - checkAll("ApplicativeError[Either[String, Int]]", ApplicativeErrorTests[Either[String, ?], String].applicativeError[Int, Int, Int]) + +} + +trait ApplicativeErrorForEitherTest extends FunSuite with Discipline { + import cats.instances.parallel._ + import cats.instances.either._ + import cats.instances.string._ + import cats.instances.int._ + import cats.instances.unit._ + import cats.instances.tuple._ + + implicit def eqV[A: Eq,B: Eq]: Eq[Validated[A, B]] = cats.data.Validated.catsDataEqForValidated + + + + checkAll("ApplicativeError[Validated[String, Int]]", ApplicativeErrorTests[Validated[String, ?], String].applicativeError[Int, Int, Int]) } From 2dc71fed3e25fe9354c27fb2b2b1c8d9b73166b1 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 28 Aug 2017 21:37:46 +0200 Subject: [PATCH 19/63] Fix mixup --- core/src/main/scala/cats/Parallel.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 140176964c..372c30e2c6 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -4,19 +4,19 @@ package cats * Some types that form a Monad, are also capable of forming an Applicative that supports parallel composition. * The Parallel type class allows us to represent this relationship. */ -trait Parallel[M[_], F[_]] { +trait Parallel[M[_], F[_]] extends Serializable { /** * The applicative instance for F[_] */ def applicative: Applicative[F] /** - * Natural Transformation from the sequential Monad M[_] to the parallel Applicative F[_]. + * Natural Transformation from the parallel Applicative F[_] to the sequential Monad M[_]. */ def sequential(implicit M: Monad[M]): F ~> M /** - * Natural Transformation from the parallel Applicative F[_] to the sequential Monad M[_]. + * Natural Transformation from the sequential Monad M[_] to the parallel Applicative F[_]. */ def parallel(implicit M: Monad[M]): M ~> F } From 00664aee1627dc5647f649bb8f338b2f44086bb2 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 29 Aug 2017 16:55:34 +0200 Subject: [PATCH 20/63] Move appError instance to Parallel companion object --- core/src/main/scala/cats/Parallel.scala | 16 ++++++++++++++++ .../main/scala/cats/instances/parallel.scala | 18 +----------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 372c30e2c6..cab4d02e9a 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -97,4 +97,20 @@ object Parallel extends ParallelArityFunctions { (ma: M[A], mb: M[B]) (implicit P: Parallel[M, F]): M[Z] = Monad[M].map(parProduct(ma, parProduct(mb, ff))) { case (a, (b, f)) => f(a, b) } + + def applicativeError[F[_], M[_], E] + (implicit P: Parallel[M, F], E: MonadError[M, E]): ApplicativeError[F, E] = new ApplicativeError[F, E] { + + def raiseError[A](e: E): F[A] = + P.parallel.apply(MonadError[M, E].raiseError(e)) + + def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { + val ma = MonadError[M, E].handleErrorWith(P.sequential.apply(fa))(f andThen P.sequential.apply) + P.parallel.apply(ma) + } + + def pure[A](x: A): F[A] = P.applicative.pure(x) + + def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = P.applicative.ap(ff)(fa) + } } diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index cfe2f68990..94ccf57923 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -3,7 +3,7 @@ package cats.instances import cats.data.{EitherT, Nested, OptionT, Validated} import cats.kernel.Semigroup import cats.syntax.either._ -import cats.{Applicative, ApplicativeError, Functor, Monad, MonadError, Parallel, ~>} +import cats.{Applicative, Functor, Monad, Parallel, ~>} trait ParallelInstances { @@ -17,22 +17,6 @@ trait ParallelInstances { λ[Either[E, ?] ~> Validated[E, ?]](_.toValidated) } - implicit def catsApplicativeErrorForParallelMonadError[F[_], M[_], E] - (implicit P: Parallel[M, F], E: MonadError[M, E]): ApplicativeError[F, E] = new ApplicativeError[F, E] { - - def raiseError[A](e: E): F[A] = - P.parallel.apply(MonadError[M, E].raiseError(e)) - - def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { - val ma = MonadError[M, E].handleErrorWith(P.sequential.apply(fa))(f andThen P.sequential.apply) - P.parallel.apply(ma) - } - - def pure[A](x: A): F[A] = P.applicative.pure(x) - - def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = P.applicative.ap(ff)(fa) - } - implicit def catsParallelForOptionTNestedOption[F[_], M[_]: Monad] (implicit P: Parallel[M, F]): Parallel[OptionT[M, ?], Nested[F, Option, ?]] = new Parallel[OptionT[M, ?], Nested[F, Option, ?]] { implicit val appF: Applicative[F] = P.applicative From f941a0c481942da5354a3d627207a7d10f99f24a Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 29 Aug 2017 22:45:16 +0200 Subject: [PATCH 21/63] Fix apperror test --- tests/src/test/scala/cats/tests/ParallelTests.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index d79489dce1..328c3e2d8f 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -74,13 +74,15 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } trait ApplicativeErrorForEitherTest extends FunSuite with Discipline { - import cats.instances.parallel._ import cats.instances.either._ + import cats.instances.parallel._ import cats.instances.string._ import cats.instances.int._ import cats.instances.unit._ import cats.instances.tuple._ + implicit val parVal = Parallel.applicativeError[Validated[String, ?], Either[String, ?], String] + implicit def eqV[A: Eq,B: Eq]: Eq[Validated[A, B]] = cats.data.Validated.catsDataEqForValidated From 123620559ec494301aa079d166cf02aa062111cb Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 30 Aug 2017 17:31:25 +0200 Subject: [PATCH 22/63] Add sequential roundtrip law --- laws/src/main/scala/cats/laws/ParallelLaws.scala | 3 +++ .../scala/cats/laws/discipline/ParallelTests.scala | 12 +++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/laws/src/main/scala/cats/laws/ParallelLaws.scala b/laws/src/main/scala/cats/laws/ParallelLaws.scala index ad79ac05e6..dfaa894d01 100644 --- a/laws/src/main/scala/cats/laws/ParallelLaws.scala +++ b/laws/src/main/scala/cats/laws/ParallelLaws.scala @@ -11,6 +11,9 @@ trait ParallelLaws[M[_], F[_]] { def parallelRoundTrip[A](ma: M[A]): IsEq[M[A]] = P.sequential(monadM)(P.parallel(monadM)(ma)) <-> ma + + def sequentialRoundTrip[A](fa: F[A]): IsEq[F[A]] = + P.parallel(monadM)(P.sequential(monadM)(fa)) <-> fa } object ParallelLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala index 8fc2af6930..0d269597ff 100644 --- a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala @@ -9,11 +9,13 @@ import org.typelevel.discipline.Laws trait ParallelTests[M[_], F[_], A] extends Laws { def laws: ParallelLaws[M, F] - def parallel(implicit ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]]): RuleSet = new DefaultRuleSet( - "parallel", - None, - "parallel round trip" -> forAll((ma: M[A]) => laws.parallelRoundTrip(ma)) - ) + def parallel(implicit ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = + new DefaultRuleSet( + "parallel", + None, + "parallel round trip" -> forAll((ma: M[A]) => laws.parallelRoundTrip(ma)), + "sequential round trip" -> forAll((fa: F[A]) => laws.sequentialRoundTrip(fa)) + ) } object ParallelTests { From 17c0bc0fcbb9a2f8afbc39d8be6e97c0253d81ce Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 2 Sep 2017 12:42:58 +0200 Subject: [PATCH 23/63] Add ZipNEL and ZipNEV and Parallel instances --- .../main/scala/cats/data/NonEmptyList.scala | 25 +++++++++++++++++++ .../main/scala/cats/data/NonEmptyVector.scala | 25 +++++++++++++++++++ .../cats/laws/discipline/Arbitrary.scala | 8 ++++++ .../test/scala/cats/tests/ParallelTests.scala | 16 ++++++++++++ 4 files changed, 74 insertions(+) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 3c86a0046f..4bc1dfdf4a 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -1,6 +1,7 @@ package cats package data +import cats.data.NonEmptyList.ZipNonEmptyList import cats.instances.list._ import cats.syntax.order._ @@ -383,6 +384,18 @@ object NonEmptyList extends NonEmptyListInstances { def fromReducible[F[_], A](fa: F[A])(implicit F: Reducible[F]): NonEmptyList[A] = F.toNonEmptyList(fa) + + class ZipNonEmptyList[A](val value: NonEmptyList[A]) extends AnyVal + + object ZipNonEmptyList { + implicit val zipNelApplicative: Applicative[ZipNonEmptyList] = new Applicative[ZipNonEmptyList] { + def pure[A](x: A): ZipNonEmptyList[A] = new ZipNonEmptyList(NonEmptyList.one(x)) + def ap[A, B](ff: ZipNonEmptyList[A => B])(fa: ZipNonEmptyList[A]): ZipNonEmptyList[B] = + new ZipNonEmptyList(ff.value.zipWith(fa.value)(_ apply _)) + } + + implicit def zipNelEq[A: Eq]: Eq[ZipNonEmptyList[A]] = Eq.by(_.value) + } } private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 { @@ -479,6 +492,18 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 new NonEmptyListOrder[A] { val A0 = A } + + implicit def catsDataParallelForNonEmptyList[A]: Parallel[NonEmptyList, ZipNonEmptyList] = + new Parallel[NonEmptyList, ZipNonEmptyList] { + + def applicative: Applicative[ZipNonEmptyList] = ZipNonEmptyList.zipNelApplicative + + def sequential(implicit M: Monad[NonEmptyList]): ZipNonEmptyList ~> NonEmptyList = + λ[ZipNonEmptyList ~> NonEmptyList](_.value) + + def parallel(implicit M: Monad[NonEmptyList]): NonEmptyList ~> ZipNonEmptyList = + λ[NonEmptyList ~> ZipNonEmptyList](nel => new ZipNonEmptyList(nel)) + } } private[data] sealed trait NonEmptyListInstances0 extends NonEmptyListInstances1 { diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 292d7252ac..5d41339a64 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -1,6 +1,7 @@ package cats package data +import cats.data.NonEmptyVector.ZipNonEmptyVector import scala.annotation.tailrec import scala.collection.immutable.{TreeSet, VectorBuilder} import cats.instances.vector._ @@ -304,6 +305,18 @@ private[data] sealed trait NonEmptyVectorInstances { implicit def catsDataSemigroupForNonEmptyVector[A]: Semigroup[NonEmptyVector[A]] = catsDataInstancesForNonEmptyVector.algebra + implicit def catsDataParallelForNonEmptyVector[A]: Parallel[NonEmptyVector, ZipNonEmptyVector] = + new Parallel[NonEmptyVector, ZipNonEmptyVector] { + + def applicative: Applicative[ZipNonEmptyVector] = ZipNonEmptyVector.zipNevApplicative + + def sequential(implicit M: Monad[NonEmptyVector]): ZipNonEmptyVector ~> NonEmptyVector = + λ[ZipNonEmptyVector ~> NonEmptyVector](_.value) + + def parallel(implicit M: Monad[NonEmptyVector]): NonEmptyVector ~> ZipNonEmptyVector = + λ[NonEmptyVector ~> ZipNonEmptyVector](nev => new ZipNonEmptyVector(nev)) + } + } object NonEmptyVector extends NonEmptyVectorInstances { @@ -328,4 +341,16 @@ object NonEmptyVector extends NonEmptyVectorInstances { def fromVectorUnsafe[A](vector: Vector[A]): NonEmptyVector[A] = if (vector.nonEmpty) new NonEmptyVector(vector) else throw new IllegalArgumentException("Cannot create NonEmptyVector from empty vector") + + class ZipNonEmptyVector[A](val value: NonEmptyVector[A]) + + object ZipNonEmptyVector { + implicit val zipNevApplicative: Applicative[ZipNonEmptyVector] = new Applicative[ZipNonEmptyVector] { + def pure[A](x: A): ZipNonEmptyVector[A] = new ZipNonEmptyVector(NonEmptyVector.one(x)) + def ap[A, B](ff: ZipNonEmptyVector[A => B])(fa: ZipNonEmptyVector[A]): ZipNonEmptyVector[B] = + new ZipNonEmptyVector(ff.value.zipWith(fa.value)(_ apply _)) + } + + implicit def zipNevEq[A: Eq]: Eq[ZipNonEmptyVector[A]] = Eq.by(_.value) + } } diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 12f5432459..ed34793cb0 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -2,6 +2,8 @@ package cats package laws package discipline +import cats.data.NonEmptyList.ZipNonEmptyList +import cats.data.NonEmptyVector.ZipNonEmptyVector import scala.util.{Failure, Success, Try} import cats.data._ import org.scalacheck.{Arbitrary, Cogen, Gen} @@ -48,12 +50,18 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsCogenForNonEmptyVector[A](implicit A: Cogen[A]): Cogen[NonEmptyVector[A]] = Cogen[Vector[A]].contramap(_.toVector) + implicit def catsLawsArbitraryForZipNonEmptyVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipNonEmptyVector[A]] = + Arbitrary(implicitly[Arbitrary[NonEmptyVector[A]]].arbitrary.map(nev => new ZipNonEmptyVector(nev))) + implicit def catsLawsArbitraryForNonEmptyList[A](implicit A: Arbitrary[A]): Arbitrary[NonEmptyList[A]] = Arbitrary(implicitly[Arbitrary[List[A]]].arbitrary.flatMap(fa => A.arbitrary.map(a => NonEmptyList(a, fa)))) implicit def catsLawsCogenForNonEmptyList[A](implicit A: Cogen[A]): Cogen[NonEmptyList[A]] = Cogen[List[A]].contramap(_.toList) + implicit def catsLawsArbitraryForZipNonEmptyList[A](implicit A: Arbitrary[A]): Arbitrary[ZipNonEmptyList[A]] = + Arbitrary(implicitly[Arbitrary[NonEmptyList[A]]].arbitrary.map(nel => new ZipNonEmptyList(nel))) + implicit def catsLawsArbitraryForEitherT[F[_], A, B](implicit F: Arbitrary[F[Either[A, B]]]): Arbitrary[EitherT[F, A, B]] = Arbitrary(F.arbitrary.map(EitherT(_))) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 328c3e2d8f..a8683ddebd 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -1,6 +1,8 @@ package cats +import cats.data.NonEmptyList.ZipNonEmptyList +import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data._ import cats.tests.CatsSuite import org.scalatest.FunSuite @@ -58,10 +60,24 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } + test("ParMap over NonEmptyList should be consistent with zip") { + forAll { (as: NonEmptyList[Int], bs: NonEmptyList[Int], cs: NonEmptyList[Int]) => + (as, bs, cs).parMapN(_ + _ + _) should === (as.zipWith(bs)(_ + _).zipWith(cs)(_ + _)) + } + } + + test("ParMap over NonEmptyVector should be consistent with zip") { + forAll { (as: NonEmptyVector[Int], bs: NonEmptyVector[Int], cs: NonEmptyVector[Int]) => + (as, bs, cs).parMapN(_ + _ + _) should === (as.zipWith(bs)(_ + _).zipWith(cs)(_ + _)) + } + } + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) + checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", ParallelTypeclassTests[NonEmptyList, ZipNonEmptyList, Int].parallel) + checkAll("Parallel[NonEmptyVector, ZipNonEmptyVector]", ParallelTypeclassTests[NonEmptyVector, ZipNonEmptyVector, Int].parallel) { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = From 15020872dc724f6e963b43b58b005bcf6f7dff26 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 3 Sep 2017 12:36:42 +0200 Subject: [PATCH 24/63] Add law for testing that pure is consistent across Parallel pairs --- laws/src/main/scala/cats/laws/ParallelLaws.scala | 3 +++ .../src/main/scala/cats/laws/discipline/ParallelTests.scala | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/ParallelLaws.scala b/laws/src/main/scala/cats/laws/ParallelLaws.scala index dfaa894d01..6a6916ae88 100644 --- a/laws/src/main/scala/cats/laws/ParallelLaws.scala +++ b/laws/src/main/scala/cats/laws/ParallelLaws.scala @@ -14,6 +14,9 @@ trait ParallelLaws[M[_], F[_]] { def sequentialRoundTrip[A](fa: F[A]): IsEq[F[A]] = P.parallel(monadM)(P.sequential(monadM)(fa)) <-> fa + + def isomorphicPure[A](a: A): IsEq[F[A]] = + P.applicative.pure(a) <-> P.parallel(monadM)(monadM.pure(a)) } object ParallelLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala index 0d269597ff..ace7d90de5 100644 --- a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala @@ -9,12 +9,14 @@ import org.typelevel.discipline.Laws trait ParallelTests[M[_], F[_], A] extends Laws { def laws: ParallelLaws[M, F] - def parallel(implicit ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = + def parallel + (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = new DefaultRuleSet( "parallel", None, "parallel round trip" -> forAll((ma: M[A]) => laws.parallelRoundTrip(ma)), - "sequential round trip" -> forAll((fa: F[A]) => laws.sequentialRoundTrip(fa)) + "sequential round trip" -> forAll((fa: F[A]) => laws.sequentialRoundTrip(fa)), + "isomorphic pure" -> forAll((a: A) => laws.isomorphicPure(a)) ) } From f1b1c1a3143855d91672dc4f4485432b199176fc Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 4 Sep 2017 16:45:49 +0200 Subject: [PATCH 25/63] Add Parallel Serializable tests --- core/src/main/scala/cats/data/NonEmptyVector.scala | 2 +- tests/src/test/scala/cats/tests/ParallelTests.scala | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 5d41339a64..7a54a92e42 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -342,7 +342,7 @@ object NonEmptyVector extends NonEmptyVectorInstances { if (vector.nonEmpty) new NonEmptyVector(vector) else throw new IllegalArgumentException("Cannot create NonEmptyVector from empty vector") - class ZipNonEmptyVector[A](val value: NonEmptyVector[A]) + class ZipNonEmptyVector[A](val value: NonEmptyVector[A]) extends Serializable object ZipNonEmptyVector { implicit val zipNevApplicative: Applicative[ZipNonEmptyVector] = new Applicative[ZipNonEmptyVector] { diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index a8683ddebd..4b906f5872 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -6,7 +6,7 @@ import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data._ import cats.tests.CatsSuite import org.scalatest.FunSuite -import cats.laws.discipline.{ApplicativeErrorTests, ParallelTests => ParallelTypeclassTests} +import cats.laws.discipline.{ApplicativeErrorTests, SerializableTests, ParallelTests => ParallelTypeclassTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -79,6 +79,8 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", ParallelTypeclassTests[NonEmptyList, ZipNonEmptyList, Int].parallel) checkAll("Parallel[NonEmptyVector, ZipNonEmptyVector]", ParallelTypeclassTests[NonEmptyVector, ZipNonEmptyVector, Int].parallel) + checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", SerializableTests.serializable(Parallel[NonEmptyList, ZipNonEmptyList])) + { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) @@ -99,7 +101,7 @@ trait ApplicativeErrorForEitherTest extends FunSuite with Discipline { implicit val parVal = Parallel.applicativeError[Validated[String, ?], Either[String, ?], String] - implicit def eqV[A: Eq,B: Eq]: Eq[Validated[A, B]] = cats.data.Validated.catsDataEqForValidated + implicit def eqV[A: Eq, B: Eq]: Eq[Validated[A, B]] = cats.data.Validated.catsDataEqForValidated From 9252e36f3ac1cb95ce3a75c7227568cd31ad824f Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 5 Sep 2017 14:52:51 +0200 Subject: [PATCH 26/63] Add EitherT Parallel instance that doesn't require a Parallel Instance for M --- .../main/scala/cats/instances/parallel.scala | 24 +++++++++++++++++-- .../test/scala/cats/tests/ParallelTests.scala | 1 + 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 94ccf57923..61b07379bd 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -6,7 +6,7 @@ import cats.syntax.either._ import cats.{Applicative, Functor, Monad, Parallel, ~>} -trait ParallelInstances { +trait ParallelInstances extends ParallelInstances1 { implicit def catsParallelForEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { def applicative: Applicative[Validated[E, ?]] = Validated.catsDataApplicativeErrorForValidated @@ -31,7 +31,7 @@ trait ParallelInstances { λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel.apply(optT.value))) } - implicit def catsParallelForEitherTNestedValidated[F[_], M[_]: Monad, E: Semigroup] + implicit def catsParallelForEitherTNestedParallelValidated[F[_], M[_]: Monad, E: Semigroup] (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] = new Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] { @@ -53,3 +53,23 @@ trait ParallelInstances { } } } + +private[instances] trait ParallelInstances1 { + implicit def catsParallelForEitherTNestedValidated[M[_]: Monad, E: Semigroup]: Parallel[EitherT[M, E, ?], Nested[M, Validated[E, ?], ?]] = + new Parallel[EitherT[M, E, ?], Nested[M, Validated[E, ?], ?]] { + + implicit val appValidated: Applicative[Validated[E, ?]] = Validated.catsDataApplicativeErrorForValidated + + def applicative: Applicative[Nested[M, Validated[E, ?], ?]] = cats.data.Nested.catsDataApplicativeForNested[M, Validated[E, ?]] + + def sequential(implicit M: Monad[EitherT[M, E, ?]]): Nested[M, Validated[E, ?], ?] ~> EitherT[M, E, ?] = + λ[Nested[M, Validated[E, ?], ?] ~> EitherT[M, E, ?]] { nested => + EitherT(Functor[M].map(nested.value)(_.toEither)) + } + + def parallel(implicit M: Monad[EitherT[M, E, ?]]): EitherT[M, E, ?]~> Nested[M, Validated[E, ?], ?] = + λ[EitherT[M, E, ?] ~> Nested[M, Validated[E, ?], ?]] { eitherT => + Nested(Functor[M].map(eitherT.value)(_.toValidated)) + } + } +} diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 4b906f5872..0bf54b7211 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -75,6 +75,7 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) + checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?], Int].parallel) checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", ParallelTypeclassTests[NonEmptyList, ZipNonEmptyList, Int].parallel) checkAll("Parallel[NonEmptyVector, ZipNonEmptyVector]", ParallelTypeclassTests[NonEmptyVector, ZipNonEmptyVector, Int].parallel) From e8eb35d412f9aaa4ddf7220ae802704ed096c94b Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 8 Sep 2017 00:09:31 +0200 Subject: [PATCH 27/63] Add ZipVector + Parallel instance --- core/src/main/scala/cats/data/ZipVector.scala | 16 ++++++++++++++++ .../src/main/scala/cats/instances/parallel.scala | 14 +++++++++++++- .../scala/cats/laws/discipline/Arbitrary.scala | 3 +++ .../test/scala/cats/tests/ParallelTests.scala | 3 ++- 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 core/src/main/scala/cats/data/ZipVector.scala diff --git a/core/src/main/scala/cats/data/ZipVector.scala b/core/src/main/scala/cats/data/ZipVector.scala new file mode 100644 index 0000000000..27de56bd3d --- /dev/null +++ b/core/src/main/scala/cats/data/ZipVector.scala @@ -0,0 +1,16 @@ +package cats.data + +import cats.{Applicative, Eq} +import cats.kernel.instances.vector._ + +class ZipVector[A](val value: Vector[A]) extends AnyVal + +object ZipVector { + implicit val catsDataApplicativeForZipVector: Applicative[ZipVector] = new Applicative[ZipVector] { + def pure[A](x: A): ZipVector[A] = new ZipVector(Vector(x)) + def ap[A, B](ff: ZipVector[A => B])(fa: ZipVector[A]): ZipVector[B] = + new ZipVector((ff.value, fa.value).zipped.map(_ apply _)) + } + + implicit def catsDataEqForZipVector[A: Eq]: Eq[ZipVector[A]] = Eq.by(_.value) +} diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 61b07379bd..6d28b49687 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -1,6 +1,6 @@ package cats.instances -import cats.data.{EitherT, Nested, OptionT, Validated} +import cats.data._ import cats.kernel.Semigroup import cats.syntax.either._ import cats.{Applicative, Functor, Monad, Parallel, ~>} @@ -31,6 +31,18 @@ trait ParallelInstances extends ParallelInstances1 { λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel.apply(optT.value))) } + implicit def catsStdParallelForZipVector[A]: Parallel[Vector, ZipVector] = + new Parallel[Vector, ZipVector] { + + def applicative: Applicative[ZipVector] = ZipVector.catsDataApplicativeForZipVector + + def sequential(implicit M: Monad[Vector]): ZipVector ~> Vector = + λ[ZipVector ~> Vector](_.value) + + def parallel(implicit M: Monad[Vector]): Vector ~> ZipVector = + λ[Vector ~> ZipVector](v => new ZipVector(v)) + } + implicit def catsParallelForEitherTNestedParallelValidated[F[_], M[_]: Monad, E: Semigroup] (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] = new Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] { diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index ed34793cb0..1bcdab47fb 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -50,6 +50,9 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsCogenForNonEmptyVector[A](implicit A: Cogen[A]): Cogen[NonEmptyVector[A]] = Cogen[Vector[A]].contramap(_.toVector) + implicit def catsLawsArbitraryForZipVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipVector[A]] = + Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.map(v => new ZipVector(v))) + implicit def catsLawsArbitraryForZipNonEmptyVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipNonEmptyVector[A]] = Arbitrary(implicitly[Arbitrary[NonEmptyVector[A]]].arbitrary.map(nev => new ZipNonEmptyVector(nev))) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 0bf54b7211..76d7715995 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -77,8 +77,9 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?], Int].parallel) checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) - checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", ParallelTypeclassTests[NonEmptyList, ZipNonEmptyList, Int].parallel) + checkAll("Parallel[Vector, ZipVector]", ParallelTypeclassTests[Vector, ZipVector, Int].parallel) checkAll("Parallel[NonEmptyVector, ZipNonEmptyVector]", ParallelTypeclassTests[NonEmptyVector, ZipNonEmptyVector, Int].parallel) + checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", ParallelTypeclassTests[NonEmptyList, ZipNonEmptyList, Int].parallel) checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", SerializableTests.serializable(Parallel[NonEmptyList, ZipNonEmptyList])) From 1a99aab3d8dda9df0d60ec10e6f2de75d81edad7 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 10 Sep 2017 17:15:26 +0200 Subject: [PATCH 28/63] Add ZipVector test --- tests/src/test/scala/cats/tests/ParallelTests.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 76d7715995..0b477012d0 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -72,6 +72,18 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } } + test("ParMap over Vector should be consistent with zip") { + forAll { (as: Vector[Int], bs: Vector[Int], cs: Vector[Int]) => + val zipped = as.zip(bs).map { + case (a, b) => a + b + }.zip(cs).map { + case (a, b) => a + b + } + + (as, bs, cs).parMapN(_ + _ + _) should === (zipped) + } + } + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) From ae03b3300afa8137f323434da6480457a3aa41a5 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 10 Sep 2017 18:18:25 +0200 Subject: [PATCH 29/63] Add scaladoc to ApplicativeError and change order of type parameters --- core/src/main/scala/cats/Parallel.scala | 8 +++++++- tests/src/test/scala/cats/tests/ParallelTests.scala | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index cab4d02e9a..9fe6a941ea 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -98,7 +98,13 @@ object Parallel extends ParallelArityFunctions { (implicit P: Parallel[M, F]): M[Z] = Monad[M].map(parProduct(ma, parProduct(mb, ff))) { case (a, (b, f)) => f(a, b) } - def applicativeError[F[_], M[_], E] + /** + * Provides an `ApplicativeError[F, E]` instance for any F, that has a `Parallel[M, F]` + * and a `MonadError[M, E]` instance. + * I.e. if you have a type M[_], that supports parallel composition through type F[_], + * then you can get `ApplicativeError[F, E]` from `MonadError[M, E]`. + */ + def applicativeError[M[_], F[_], E] (implicit P: Parallel[M, F], E: MonadError[M, E]): ApplicativeError[F, E] = new ApplicativeError[F, E] { def raiseError[A](e: E): F[A] = diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 0b477012d0..9438283643 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -113,7 +113,7 @@ trait ApplicativeErrorForEitherTest extends FunSuite with Discipline { import cats.instances.unit._ import cats.instances.tuple._ - implicit val parVal = Parallel.applicativeError[Validated[String, ?], Either[String, ?], String] + implicit val parVal = Parallel.applicativeError[Either[String, ?], Validated[String, ?], String] implicit def eqV[A: Eq, B: Eq]: Eq[Validated[A, B]] = cats.data.Validated.catsDataEqForValidated From 11caba0b04e227cc4e8430a369bf0cd89c53e49e Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 10 Sep 2017 18:19:12 +0200 Subject: [PATCH 30/63] Add Parallel#identity function --- core/src/main/scala/cats/Parallel.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 9fe6a941ea..ed4d4e199f 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -1,5 +1,7 @@ package cats +import cats.arrow.FunctionK + /** * Some types that form a Monad, are also capable of forming an Applicative that supports parallel composition. * The Parallel type class allows us to represent this relationship. @@ -119,4 +121,19 @@ object Parallel extends ParallelArityFunctions { def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = P.applicative.ap(ff)(fa) } + + /** + * A Parallel instance for any type `M[_]` that supports parallel composition through itself. + * Can also be used for giving `Parallel` instances to types that do not support parallel composition, + * but are required to have an instance of `Parallel` defined, + * in which case parallel composition will actually be sequential. + */ + def identity[M[_]: Monad]: Parallel[M, M] = new Parallel[M, M] { + + def applicative: Applicative[M] = implicitly[Monad[M]] + + def sequential(implicit M: Monad[M]): M ~> M = FunctionK.id + + def parallel(implicit M: Monad[M]): M ~> M = FunctionK.id + } } From 1027a4193d8bc4920a4b1b5f27c51d29f612c4f4 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 10 Sep 2017 18:19:35 +0200 Subject: [PATCH 31/63] Add identity instances for Future and Id --- core/src/main/scala/cats/instances/future.scala | 5 +++++ core/src/main/scala/cats/package.scala | 2 ++ js/src/test/scala/cats/tests/FutureTests.scala | 1 + jvm/src/test/scala/cats/tests/FutureTests.scala | 1 + tests/src/test/scala/cats/tests/ParallelTests.scala | 2 ++ 5 files changed, 11 insertions(+) diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index 2c62956816..f8c6458142 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -35,6 +35,11 @@ trait FutureInstances extends FutureInstances1 { private[instances] sealed trait FutureInstances1 extends FutureInstances2 { implicit def catsStdMonoidForFuture[A: Monoid](implicit ec: ExecutionContext): Monoid[Future[A]] = new FutureMonoid[A] + + implicit def catsStdParallelForFuture[A](implicit ec: ExecutionContext): Parallel[Future, Future] = { + implicit val M: Monad[Future] = cats.instances.future.catsStdInstancesForFuture + Parallel.identity[Future] + } } private[instances] sealed trait FutureInstances2 { diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 59fab09423..c7b16c6aa4 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -75,6 +75,8 @@ package object cats { override def isEmpty[A](fa: Id[A]): Boolean = false } + implicit val catsParallelForId: Parallel[Id, Id] = Parallel.identity + type Eq[A] = cats.kernel.Eq[A] type PartialOrder[A] = cats.kernel.PartialOrder[A] type Order[A] = cats.kernel.Order[A] diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 61f4c1010d..dbe045a000 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -55,6 +55,7 @@ class FutureTests extends CatsSuite { checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) + checkAll("Parallel[Future, Future]", ParallelTests[Future, Future, Int].parallel) { implicit val F = ListWrapper.semigroup[Int] diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index bf011071a8..1cc66c0bde 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -41,6 +41,7 @@ class FutureTests extends CatsSuite { checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) checkAll("Future", CoflatMapTests[Future].coflatMap[Int, Int, Int]) + checkAll("Parallel[Future, Future]", ParallelTests[Future, Future, Int].parallel) { implicit val F = ListWrapper.semigroup[Int] diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 9438283643..79618dba38 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -93,6 +93,8 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { checkAll("Parallel[NonEmptyVector, ZipNonEmptyVector]", ParallelTypeclassTests[NonEmptyVector, ZipNonEmptyVector, Int].parallel) checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", ParallelTypeclassTests[NonEmptyList, ZipNonEmptyList, Int].parallel) + checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id, Int].parallel) + checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", SerializableTests.serializable(Parallel[NonEmptyList, ZipNonEmptyList])) { From 942a152886ab1c68d32029df3d13974f9af9bf68 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 12 Sep 2017 09:22:46 +0200 Subject: [PATCH 32/63] Simplify parAp2 implementation --- core/src/main/scala/cats/Parallel.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index ed4d4e199f..7035a66e3f 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -97,9 +97,9 @@ object Parallel extends ParallelArityFunctions { */ def parAp2[M[_]: Monad, F[_], A, B, Z](ff: M[(A, B) => Z]) (ma: M[A], mb: M[B]) - (implicit P: Parallel[M, F]): M[Z] = - Monad[M].map(parProduct(ma, parProduct(mb, ff))) { case (a, (b, f)) => f(a, b) } - + (implicit P: Parallel[M, F]): M[Z] = P.sequential.apply( + P.applicative.ap2(P.parallel.apply(ff))(P.parallel.apply(ma), P.parallel.apply(mb)) + ) /** * Provides an `ApplicativeError[F, E]` instance for any F, that has a `Parallel[M, F]` * and a `MonadError[M, E]` instance. From 6354e08269d2438b0049d8d165236d394fcb0308 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 12 Sep 2017 22:24:20 +0200 Subject: [PATCH 33/63] Refactor Parallel --- core/src/main/scala/cats/Parallel.scala | 52 ++++++++++++------- core/src/main/scala/cats/data/Kleisli.scala | 8 +-- .../main/scala/cats/data/NonEmptyList.scala | 6 ++- .../main/scala/cats/data/NonEmptyVector.scala | 5 +- core/src/main/scala/cats/data/WriterT.scala | 9 ++-- .../main/scala/cats/instances/parallel.scala | 42 ++++++++++----- .../main/scala/cats/laws/ParallelLaws.scala | 7 ++- 7 files changed, 83 insertions(+), 46 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 7035a66e3f..10460a9b08 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -12,15 +12,20 @@ trait Parallel[M[_], F[_]] extends Serializable { */ def applicative: Applicative[F] + /** + * The monad instance for M[_] + */ + def monad: Monad[M] + /** * Natural Transformation from the parallel Applicative F[_] to the sequential Monad M[_]. */ - def sequential(implicit M: Monad[M]): F ~> M + def sequential: F ~> M /** * Natural Transformation from the sequential Monad M[_] to the parallel Applicative F[_]. */ - def parallel(implicit M: Monad[M]): M ~> F + def parallel: M ~> F } object Parallel extends ParallelArityFunctions { @@ -31,56 +36,61 @@ object Parallel extends ParallelArityFunctions { * Like `Traverse[A].sequence`, but uses the applicative instance * corresponding to the Parallel instance instead. */ - def parSequence[T[_]: Traverse, M[_]: Monad, F[_], A] + def parSequence[T[_]: Traverse, M[_], F[_], A] (tma: T[M[A]])(implicit P: Parallel[M, F]): M[T[A]] = { implicit val F = P.applicative + implicit val M = P.monad val fta: F[T[A]] = Traverse[T].traverse(tma)(P.parallel.apply) - P.sequential.apply(fta) + P.sequential(fta) } /** * Like `Traverse[A].traverse`, but uses the applicative instance * corresponding to the Parallel instance instead. */ - def parTraverse[T[_]: Traverse, M[_]: Monad, F[_], A, B] + def parTraverse[T[_]: Traverse, M[_], F[_], A, B] (ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[T[B]] = { implicit val F = P.applicative + implicit val M = P.monad val gtb: F[T[B]] = Traverse[T].traverse(ta)(f andThen P.parallel.apply) - P.sequential.apply(gtb) + P.sequential(gtb) } /** * Like `Foldable[A].sequence_`, but uses the applicative instance * corresponding to the Parallel instance instead. */ - def parSequence_[T[_]: Foldable, M[_]: Monad, F[_], A] + def parSequence_[T[_]: Foldable, M[_], F[_], A] (tma: T[M[A]])(implicit P: Parallel[M, F]): M[Unit] = { implicit val F = P.applicative + implicit val M = P.monad val fu: F[Unit] = Foldable[T].traverse_(tma)(P.parallel.apply) - P.sequential.apply(fu) + P.sequential(fu) } /** * Like `Foldable[A].traverse_`, but uses the applicative instance * corresponding to the Parallel instance instead. */ - def parTraverse_[T[_]: Foldable, M[_]: Monad, F[_], A, B] + def parTraverse_[T[_]: Foldable, M[_], F[_], A, B] (ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[Unit] = { implicit val F = P.applicative + implicit val M = P.monad val gtb: F[Unit] = Foldable[T].traverse_(ta)(f andThen P.parallel.apply) - P.sequential.apply(gtb) + P.sequential(gtb) } /** * Like `Applicative[F].ap`, but uses the applicative instance * corresponding to the Parallel instance instead. */ - def parAp[M[_]: Monad, F[_], A, B](mf: M[A => B]) + def parAp[M[_], F[_], A, B](mf: M[A => B]) (ma: M[A]) (implicit P: Parallel[M, F]): M[B] = { implicit val F = P.applicative - val fb = Applicative[F].ap(P.parallel.apply(mf))(P.parallel.apply(ma)) - P.sequential.apply(fb) + implicit val M = P.monad + val fb = Applicative[F].ap(P.parallel(mf))(P.parallel(ma)) + P.sequential(fb) } /** @@ -97,9 +107,13 @@ object Parallel extends ParallelArityFunctions { */ def parAp2[M[_]: Monad, F[_], A, B, Z](ff: M[(A, B) => Z]) (ma: M[A], mb: M[B]) - (implicit P: Parallel[M, F]): M[Z] = P.sequential.apply( - P.applicative.ap2(P.parallel.apply(ff))(P.parallel.apply(ma), P.parallel.apply(mb)) - ) + (implicit P: Parallel[M, F]): M[Z] = { + implicit val M = P.monad + P.sequential.apply( + P.applicative.ap2(P.parallel(ff))(P.parallel(ma), P.parallel(mb)) + ) + } + /** * Provides an `ApplicativeError[F, E]` instance for any F, that has a `Parallel[M, F]` * and a `MonadError[M, E]` instance. @@ -130,10 +144,12 @@ object Parallel extends ParallelArityFunctions { */ def identity[M[_]: Monad]: Parallel[M, M] = new Parallel[M, M] { + def monad: Monad[M] = implicitly[Monad[M]] + def applicative: Applicative[M] = implicitly[Monad[M]] - def sequential(implicit M: Monad[M]): M ~> M = FunctionK.id + def sequential: M ~> M = FunctionK.id - def parallel(implicit M: Monad[M]): M ~> M = FunctionK.id + def parallel: M ~> M = FunctionK.id } } diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index 093a51789c..72010ab533 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -129,15 +129,17 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2 implicit def catsDataMonadForKleisli[F[_], A](implicit M: Monad[F]): Monad[Kleisli[F, A, ?]] = new KleisliMonad[F, A] { def F: Monad[F] = M } - implicit def catsDataParallelForKleisli[F[_], M[_]: Monad, A] + implicit def catsDataParallelForKleisli[F[_], M[_], A] (implicit P: Parallel[M, F]): Parallel[Kleisli[M, A, ?], Kleisli[F, A, ?]] = new Parallel[Kleisli[M, A, ?], Kleisli[F, A, ?]]{ implicit val appF = P.applicative + implicit val monadM = P.monad def applicative: Applicative[Kleisli[F, A, ?]] = catsDataApplicativeForKleisli + def monad: Monad[Kleisli[M, A, ?]] = catsDataMonadForKleisli - def sequential(implicit M: Monad[Kleisli[M, A, ?]]): Kleisli[F, A, ?] ~> Kleisli[M, A, ?] = + def sequential: Kleisli[F, A, ?] ~> Kleisli[M, A, ?] = λ[Kleisli[F, A, ?] ~> Kleisli[M, A, ?]](_.transform(P.sequential)) - def parallel(implicit M: Monad[Kleisli[M, A, ?]]): Kleisli[M, A, ?] ~> Kleisli[F, A, ?] = + def parallel: Kleisli[M, A, ?] ~> Kleisli[F, A, ?] = λ[Kleisli[M, A, ?] ~> Kleisli[F, A, ?]](_.transform(P.parallel)) } } diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 4bc1dfdf4a..0eaa6a11c0 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -496,12 +496,14 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 implicit def catsDataParallelForNonEmptyList[A]: Parallel[NonEmptyList, ZipNonEmptyList] = new Parallel[NonEmptyList, ZipNonEmptyList] { + def monad: Monad[NonEmptyList] = NonEmptyList.catsDataInstancesForNonEmptyList + def applicative: Applicative[ZipNonEmptyList] = ZipNonEmptyList.zipNelApplicative - def sequential(implicit M: Monad[NonEmptyList]): ZipNonEmptyList ~> NonEmptyList = + def sequential: ZipNonEmptyList ~> NonEmptyList = λ[ZipNonEmptyList ~> NonEmptyList](_.value) - def parallel(implicit M: Monad[NonEmptyList]): NonEmptyList ~> ZipNonEmptyList = + def parallel: NonEmptyList ~> ZipNonEmptyList = λ[NonEmptyList ~> ZipNonEmptyList](nel => new ZipNonEmptyList(nel)) } } diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 7a54a92e42..4f8bdc8d13 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -309,11 +309,12 @@ private[data] sealed trait NonEmptyVectorInstances { new Parallel[NonEmptyVector, ZipNonEmptyVector] { def applicative: Applicative[ZipNonEmptyVector] = ZipNonEmptyVector.zipNevApplicative + def monad: Monad[NonEmptyVector] = NonEmptyVector.catsDataInstancesForNonEmptyVector - def sequential(implicit M: Monad[NonEmptyVector]): ZipNonEmptyVector ~> NonEmptyVector = + def sequential: ZipNonEmptyVector ~> NonEmptyVector = λ[ZipNonEmptyVector ~> NonEmptyVector](_.value) - def parallel(implicit M: Monad[NonEmptyVector]): NonEmptyVector ~> ZipNonEmptyVector = + def parallel: NonEmptyVector ~> ZipNonEmptyVector = λ[NonEmptyVector ~> ZipNonEmptyVector](nev => new ZipNonEmptyVector(nev)) } diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 2f811ac42e..558fbfc179 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -81,15 +81,18 @@ private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 implicit val L0: Monoid[L] = L } - implicit def catsDataParallelForWriterT[F[_], M[_]: Monad, L: Monoid] + implicit def catsDataParallelForWriterT[F[_], M[_], L: Monoid] (implicit P: Parallel[M, F]): Parallel[WriterT[M, L, ?], WriterT[F, L, ?]] = new Parallel[WriterT[M, L, ?], WriterT[F, L, ?]]{ implicit val appF = P.applicative + implicit val monadM = P.monad + def applicative: Applicative[WriterT[F, L, ?]] = catsDataApplicativeForWriterT + def monad: Monad[WriterT[M, L, ?]] = catsDataMonadForWriterT - def sequential(implicit M: Monad[WriterT[M, L, ?]]): WriterT[F, L, ?] ~> WriterT[M, L, ?] = + def sequential: WriterT[F, L, ?] ~> WriterT[M, L, ?] = λ[WriterT[F, L, ?] ~> WriterT[M, L, ?]](wfl => WriterT(P.sequential.apply(wfl.run))) - def parallel(implicit M: Monad[WriterT[M, L, ?]]): WriterT[M, L, ?] ~> WriterT[F, L, ?] = + def parallel: WriterT[M, L, ?] ~> WriterT[F, L, ?] = λ[WriterT[M, L, ?] ~> WriterT[F, L, ?]](wml => WriterT(P.parallel.apply(wml.run))) } diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 6d28b49687..21dcf6eb7e 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -8,57 +8,68 @@ import cats.{Applicative, Functor, Monad, Parallel, ~>} trait ParallelInstances extends ParallelInstances1 { implicit def catsParallelForEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { + def applicative: Applicative[Validated[E, ?]] = Validated.catsDataApplicativeErrorForValidated + def monad: Monad[Either[E, ?]] = cats.instances.either.catsStdInstancesForEither - def sequential(implicit M: Monad[Either[E, ?]]): Validated[E, ?] ~> Either[E, ?] = + def sequential: Validated[E, ?] ~> Either[E, ?] = λ[Validated[E, ?] ~> Either[E, ?]](_.toEither) - def parallel(implicit M: Monad[Either[E, ?]]): Either[E, ?] ~> Validated[E, ?] = + def parallel: Either[E, ?] ~> Validated[E, ?] = λ[Either[E, ?] ~> Validated[E, ?]](_.toValidated) } - implicit def catsParallelForOptionTNestedOption[F[_], M[_]: Monad] + implicit def catsParallelForOptionTNestedOption[F[_], M[_]] (implicit P: Parallel[M, F]): Parallel[OptionT[M, ?], Nested[F, Option, ?]] = new Parallel[OptionT[M, ?], Nested[F, Option, ?]] { + implicit val appF: Applicative[F] = P.applicative + implicit val monadM: Monad[M] = P.monad implicit val appOption: Applicative[Option] = cats.instances.option.catsStdInstancesForOption def applicative: Applicative[Nested[F, Option, ?]] = cats.data.Nested.catsDataApplicativeForNested[F, Option] - def sequential(implicit M: Monad[OptionT[M, ?]]): Nested[F, Option, ?] ~> OptionT[M, ?] = + def monad: Monad[OptionT[M, ?]] = cats.data.OptionT.catsDataMonadForOptionT[M] + + def sequential: Nested[F, Option, ?] ~> OptionT[M, ?] = λ[Nested[F, Option, ?] ~> OptionT[M, ?]](nested => OptionT(P.sequential.apply(nested.value))) - def parallel(implicit M: Monad[OptionT[M, ?]]): OptionT[M, ?]~> Nested[F, Option, ?] = + def parallel: OptionT[M, ?]~> Nested[F, Option, ?] = λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel.apply(optT.value))) } implicit def catsStdParallelForZipVector[A]: Parallel[Vector, ZipVector] = new Parallel[Vector, ZipVector] { + def monad: Monad[Vector] = cats.instances.vector.catsStdInstancesForVector def applicative: Applicative[ZipVector] = ZipVector.catsDataApplicativeForZipVector - def sequential(implicit M: Monad[Vector]): ZipVector ~> Vector = + def sequential: ZipVector ~> Vector = λ[ZipVector ~> Vector](_.value) - def parallel(implicit M: Monad[Vector]): Vector ~> ZipVector = + def parallel: Vector ~> ZipVector = λ[Vector ~> ZipVector](v => new ZipVector(v)) } - implicit def catsParallelForEitherTNestedParallelValidated[F[_], M[_]: Monad, E: Semigroup] + implicit def catsParallelForEitherTNestedParallelValidated[F[_], M[_], E: Semigroup] (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] = new Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] { implicit val appF: Applicative[F] = P.applicative + implicit val monadM: Monad[M] = P.monad implicit val appValidated: Applicative[Validated[E, ?]] = Validated.catsDataApplicativeErrorForValidated + implicit val monadEither: Monad[Either[E, ?]] = cats.instances.either.catsStdInstancesForEither def applicative: Applicative[Nested[F, Validated[E, ?], ?]] = cats.data.Nested.catsDataApplicativeForNested[F, Validated[E, ?]] - def sequential(implicit M: Monad[EitherT[M, E, ?]]): Nested[F, Validated[E, ?], ?] ~> EitherT[M, E, ?] = + def monad: Monad[EitherT[M, E, ?]] = cats.data.EitherT.catsDataMonadErrorForEitherT + + def sequential: Nested[F, Validated[E, ?], ?] ~> EitherT[M, E, ?] = λ[Nested[F, Validated[E, ?], ?] ~> EitherT[M, E, ?]] { nested => val mva = P.sequential.apply(nested.value) EitherT(Functor[M].map(mva)(_.toEither)) } - def parallel(implicit M: Monad[EitherT[M, E, ?]]): EitherT[M, E, ?]~> Nested[F, Validated[E, ?], ?] = + def parallel: EitherT[M, E, ?]~> Nested[F, Validated[E, ?], ?] = λ[EitherT[M, E, ?] ~> Nested[F, Validated[E, ?], ?]] { eitherT => val fea = P.parallel.apply(eitherT.value) Nested(Functor[F].map(fea)(_.toValidated)) @@ -71,17 +82,20 @@ private[instances] trait ParallelInstances1 { new Parallel[EitherT[M, E, ?], Nested[M, Validated[E, ?], ?]] { implicit val appValidated: Applicative[Validated[E, ?]] = Validated.catsDataApplicativeErrorForValidated + implicit val monadEither: Monad[Either[E, ?]] = cats.instances.either.catsStdInstancesForEither def applicative: Applicative[Nested[M, Validated[E, ?], ?]] = cats.data.Nested.catsDataApplicativeForNested[M, Validated[E, ?]] - def sequential(implicit M: Monad[EitherT[M, E, ?]]): Nested[M, Validated[E, ?], ?] ~> EitherT[M, E, ?] = + def monad: Monad[EitherT[M, E, ?]] = cats.data.EitherT.catsDataMonadErrorForEitherT + + def sequential: Nested[M, Validated[E, ?], ?] ~> EitherT[M, E, ?] = λ[Nested[M, Validated[E, ?], ?] ~> EitherT[M, E, ?]] { nested => - EitherT(Functor[M].map(nested.value)(_.toEither)) + EitherT(Monad[M].map(nested.value)(_.toEither)) } - def parallel(implicit M: Monad[EitherT[M, E, ?]]): EitherT[M, E, ?]~> Nested[M, Validated[E, ?], ?] = + def parallel: EitherT[M, E, ?]~> Nested[M, Validated[E, ?], ?] = λ[EitherT[M, E, ?] ~> Nested[M, Validated[E, ?], ?]] { eitherT => - Nested(Functor[M].map(eitherT.value)(_.toValidated)) + Nested(Monad[M].map(eitherT.value)(_.toValidated)) } } } diff --git a/laws/src/main/scala/cats/laws/ParallelLaws.scala b/laws/src/main/scala/cats/laws/ParallelLaws.scala index 6a6916ae88..f99a300203 100644 --- a/laws/src/main/scala/cats/laws/ParallelLaws.scala +++ b/laws/src/main/scala/cats/laws/ParallelLaws.scala @@ -6,17 +6,16 @@ package laws * Laws that must be obeyed by any `cats.Parallel`. */ trait ParallelLaws[M[_], F[_]] { - def monadM: Monad[M] def P: Parallel[M, F] def parallelRoundTrip[A](ma: M[A]): IsEq[M[A]] = - P.sequential(monadM)(P.parallel(monadM)(ma)) <-> ma + P.sequential(P.parallel(ma)) <-> ma def sequentialRoundTrip[A](fa: F[A]): IsEq[F[A]] = - P.parallel(monadM)(P.sequential(monadM)(fa)) <-> fa + P.parallel(P.sequential(fa)) <-> fa def isomorphicPure[A](a: A): IsEq[F[A]] = - P.applicative.pure(a) <-> P.parallel(monadM)(monadM.pure(a)) + P.applicative.pure(a) <-> P.parallel(P.monad.pure(a)) } object ParallelLaws { From 26b79308a61dcb6c8fe2ae296bbd1bf0a63d028c Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 12 Sep 2017 22:34:46 +0200 Subject: [PATCH 34/63] Add applicativeError instace method --- core/src/main/scala/cats/Parallel.scala | 9 +++++++++ tests/src/test/scala/cats/tests/ParallelTests.scala | 13 ++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 10460a9b08..85aee79ac4 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -26,6 +26,15 @@ trait Parallel[M[_], F[_]] extends Serializable { * Natural Transformation from the sequential Monad M[_] to the parallel Applicative F[_]. */ def parallel: M ~> F + + /** + * Provides an `ApplicativeError[F, E]` instance for any F, that has a `Parallel[M, F]` + * and a `MonadError[M, E]` instance. + * I.e. if you have a type M[_], that supports parallel composition through type F[_], + * then you can get `ApplicativeError[F, E]` from `MonadError[M, E]`. + */ + def applicativeError[E](implicit E: MonadError[M, E]): ApplicativeError[F, E] = + Parallel.applicativeError[M, F, E](this, E) } object Parallel extends ParallelArityFunctions { diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 79618dba38..c67d6354fb 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -108,6 +108,7 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } trait ApplicativeErrorForEitherTest extends FunSuite with Discipline { + import cats.instances.either._ import cats.instances.parallel._ import cats.instances.string._ @@ -115,11 +116,17 @@ trait ApplicativeErrorForEitherTest extends FunSuite with Discipline { import cats.instances.unit._ import cats.instances.tuple._ - implicit val parVal = Parallel.applicativeError[Either[String, ?], Validated[String, ?], String] - implicit def eqV[A: Eq, B: Eq]: Eq[Validated[A, B]] = cats.data.Validated.catsDataEqForValidated + { + implicit val parVal = Parallel.applicativeError[Either[String, ?], Validated[String, ?], String] + checkAll("ApplicativeError[Validated[String, Int]]", ApplicativeErrorTests[Validated[String, ?], String].applicativeError[Int, Int, Int]) + } - checkAll("ApplicativeError[Validated[String, Int]]", ApplicativeErrorTests[Validated[String, ?], String].applicativeError[Int, Int, Int]) + { + implicit val parVal = Parallel[Either[String, ?], Validated[String, ?]].applicativeError + + checkAll("Parallel[Validated[String, Int]].applicativeError", ApplicativeErrorTests[Validated[String, ?], String].applicativeError[Int, Int, Int]) + } } From 9e3891dbb8f1bbded29f66cdce5343fe41a97542 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 15 Sep 2017 11:21:12 +0200 Subject: [PATCH 35/63] Reverse applicativeError and remove redundant .apply --- core/src/main/scala/cats/Parallel.scala | 33 +++++++++---------- core/src/main/scala/cats/data/WriterT.scala | 4 +-- .../main/scala/cats/instances/parallel.scala | 8 ++--- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 85aee79ac4..3c9b8490ae 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -33,8 +33,20 @@ trait Parallel[M[_], F[_]] extends Serializable { * I.e. if you have a type M[_], that supports parallel composition through type F[_], * then you can get `ApplicativeError[F, E]` from `MonadError[M, E]`. */ - def applicativeError[E](implicit E: MonadError[M, E]): ApplicativeError[F, E] = - Parallel.applicativeError[M, F, E](this, E) + def applicativeError[E](implicit E: MonadError[M, E]): ApplicativeError[F, E] = new ApplicativeError[F, E] { + + def raiseError[A](e: E): F[A] = + parallel(MonadError[M, E].raiseError(e)) + + def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { + val ma = MonadError[M, E].handleErrorWith(sequential(fa))(f andThen sequential.apply) + parallel(ma) + } + + def pure[A](x: A): F[A] = applicative.pure(x) + + def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = applicative.ap(ff)(fa) + } } object Parallel extends ParallelArityFunctions { @@ -118,7 +130,7 @@ object Parallel extends ParallelArityFunctions { (ma: M[A], mb: M[B]) (implicit P: Parallel[M, F]): M[Z] = { implicit val M = P.monad - P.sequential.apply( + P.sequential( P.applicative.ap2(P.parallel(ff))(P.parallel(ma), P.parallel(mb)) ) } @@ -130,20 +142,7 @@ object Parallel extends ParallelArityFunctions { * then you can get `ApplicativeError[F, E]` from `MonadError[M, E]`. */ def applicativeError[M[_], F[_], E] - (implicit P: Parallel[M, F], E: MonadError[M, E]): ApplicativeError[F, E] = new ApplicativeError[F, E] { - - def raiseError[A](e: E): F[A] = - P.parallel.apply(MonadError[M, E].raiseError(e)) - - def handleErrorWith[A](fa: F[A])(f: (E) => F[A]): F[A] = { - val ma = MonadError[M, E].handleErrorWith(P.sequential.apply(fa))(f andThen P.sequential.apply) - P.parallel.apply(ma) - } - - def pure[A](x: A): F[A] = P.applicative.pure(x) - - def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = P.applicative.ap(ff)(fa) - } + (implicit P: Parallel[M, F], E: MonadError[M, E]): ApplicativeError[F, E] = P.applicativeError /** * A Parallel instance for any type `M[_]` that supports parallel composition through itself. diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 558fbfc179..1d44399a03 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -90,10 +90,10 @@ private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 def monad: Monad[WriterT[M, L, ?]] = catsDataMonadForWriterT def sequential: WriterT[F, L, ?] ~> WriterT[M, L, ?] = - λ[WriterT[F, L, ?] ~> WriterT[M, L, ?]](wfl => WriterT(P.sequential.apply(wfl.run))) + λ[WriterT[F, L, ?] ~> WriterT[M, L, ?]](wfl => WriterT(P.sequential(wfl.run))) def parallel: WriterT[M, L, ?] ~> WriterT[F, L, ?] = - λ[WriterT[M, L, ?] ~> WriterT[F, L, ?]](wml => WriterT(P.parallel.apply(wml.run))) + λ[WriterT[M, L, ?] ~> WriterT[F, L, ?]](wml => WriterT(P.parallel(wml.run))) } implicit def catsDataEqForWriterTId[L: Eq, V: Eq]: Eq[WriterT[Id, L, V]] = diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 21dcf6eb7e..24889e73f1 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -31,10 +31,10 @@ trait ParallelInstances extends ParallelInstances1 { def monad: Monad[OptionT[M, ?]] = cats.data.OptionT.catsDataMonadForOptionT[M] def sequential: Nested[F, Option, ?] ~> OptionT[M, ?] = - λ[Nested[F, Option, ?] ~> OptionT[M, ?]](nested => OptionT(P.sequential.apply(nested.value))) + λ[Nested[F, Option, ?] ~> OptionT[M, ?]](nested => OptionT(P.sequential(nested.value))) def parallel: OptionT[M, ?]~> Nested[F, Option, ?] = - λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel.apply(optT.value))) + λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel(optT.value))) } implicit def catsStdParallelForZipVector[A]: Parallel[Vector, ZipVector] = @@ -65,13 +65,13 @@ trait ParallelInstances extends ParallelInstances1 { def sequential: Nested[F, Validated[E, ?], ?] ~> EitherT[M, E, ?] = λ[Nested[F, Validated[E, ?], ?] ~> EitherT[M, E, ?]] { nested => - val mva = P.sequential.apply(nested.value) + val mva = P.sequential(nested.value) EitherT(Functor[M].map(mva)(_.toEither)) } def parallel: EitherT[M, E, ?]~> Nested[F, Validated[E, ?], ?] = λ[EitherT[M, E, ?] ~> Nested[F, Validated[E, ?], ?]] { eitherT => - val fea = P.parallel.apply(eitherT.value) + val fea = P.parallel(eitherT.value) Nested(Functor[F].map(fea)(_.toValidated)) } } From 4dbc99591d5b4ee5978b68364b580f8b4b85bf26 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 15 Sep 2017 11:35:58 +0200 Subject: [PATCH 36/63] Simplify further --- core/src/main/scala/cats/Parallel.scala | 34 ++++++------------- .../main/scala/cats/data/NonEmptyList.scala | 2 ++ .../main/scala/cats/data/NonEmptyVector.scala | 2 ++ core/src/main/scala/cats/data/ZipVector.scala | 3 ++ 4 files changed, 17 insertions(+), 24 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 3c9b8490ae..3a0fe413fe 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -59,9 +59,7 @@ object Parallel extends ParallelArityFunctions { */ def parSequence[T[_]: Traverse, M[_], F[_], A] (tma: T[M[A]])(implicit P: Parallel[M, F]): M[T[A]] = { - implicit val F = P.applicative - implicit val M = P.monad - val fta: F[T[A]] = Traverse[T].traverse(tma)(P.parallel.apply) + val fta: F[T[A]] = Traverse[T].traverse(tma)(P.parallel.apply)(P.applicative) P.sequential(fta) } @@ -71,9 +69,7 @@ object Parallel extends ParallelArityFunctions { */ def parTraverse[T[_]: Traverse, M[_], F[_], A, B] (ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[T[B]] = { - implicit val F = P.applicative - implicit val M = P.monad - val gtb: F[T[B]] = Traverse[T].traverse(ta)(f andThen P.parallel.apply) + val gtb: F[T[B]] = Traverse[T].traverse(ta)(f andThen P.parallel.apply)(P.applicative) P.sequential(gtb) } @@ -83,9 +79,7 @@ object Parallel extends ParallelArityFunctions { */ def parSequence_[T[_]: Foldable, M[_], F[_], A] (tma: T[M[A]])(implicit P: Parallel[M, F]): M[Unit] = { - implicit val F = P.applicative - implicit val M = P.monad - val fu: F[Unit] = Foldable[T].traverse_(tma)(P.parallel.apply) + val fu: F[Unit] = Foldable[T].traverse_(tma)(P.parallel.apply)(P.applicative) P.sequential(fu) } @@ -95,9 +89,7 @@ object Parallel extends ParallelArityFunctions { */ def parTraverse_[T[_]: Foldable, M[_], F[_], A, B] (ta: T[A])(f: A => M[B])(implicit P: Parallel[M, F]): M[Unit] = { - implicit val F = P.applicative - implicit val M = P.monad - val gtb: F[Unit] = Foldable[T].traverse_(ta)(f andThen P.parallel.apply) + val gtb: F[Unit] = Foldable[T].traverse_(ta)(f andThen P.parallel.apply)(P.applicative) P.sequential(gtb) } @@ -107,33 +99,27 @@ object Parallel extends ParallelArityFunctions { */ def parAp[M[_], F[_], A, B](mf: M[A => B]) (ma: M[A]) - (implicit P: Parallel[M, F]): M[B] = { - implicit val F = P.applicative - implicit val M = P.monad - val fb = Applicative[F].ap(P.parallel(mf))(P.parallel(ma)) - P.sequential(fb) - } + (implicit P: Parallel[M, F]): M[B] = + P.sequential(P.applicative.ap(P.parallel(mf))(P.parallel(ma))) /** * Like `Applicative[F].product`, but uses the applicative instance * corresponding to the Parallel instance instead. */ - def parProduct[M[_]: Monad, F[_], A, B](ma: M[A], mb: M[B]) + def parProduct[M[_], F[_], A, B](ma: M[A], mb: M[B]) (implicit P: Parallel[M, F]): M[(A, B)] = - parAp(Monad[M].map(ma)(a => (b: B) => (a, b)))(mb) + P.sequential(P.applicative.product(P.parallel(ma), P.parallel(mb))) /** * Like `Applicative[F].ap2`, but uses the applicative instance * corresponding to the Parallel instance instead. */ - def parAp2[M[_]: Monad, F[_], A, B, Z](ff: M[(A, B) => Z]) + def parAp2[M[_], F[_], A, B, Z](ff: M[(A, B) => Z]) (ma: M[A], mb: M[B]) - (implicit P: Parallel[M, F]): M[Z] = { - implicit val M = P.monad + (implicit P: Parallel[M, F]): M[Z] = P.sequential( P.applicative.ap2(P.parallel(ff))(P.parallel(ma), P.parallel(mb)) ) - } /** * Provides an `ApplicativeError[F, E]` instance for any F, that has a `Parallel[M, F]` diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 0eaa6a11c0..86a6aeb55d 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -392,6 +392,8 @@ object NonEmptyList extends NonEmptyListInstances { def pure[A](x: A): ZipNonEmptyList[A] = new ZipNonEmptyList(NonEmptyList.one(x)) def ap[A, B](ff: ZipNonEmptyList[A => B])(fa: ZipNonEmptyList[A]): ZipNonEmptyList[B] = new ZipNonEmptyList(ff.value.zipWith(fa.value)(_ apply _)) + override def product[A, B](fa: ZipNonEmptyList[A], fb: ZipNonEmptyList[B]): ZipNonEmptyList[(A, B)] = + new ZipNonEmptyList(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) } implicit def zipNelEq[A: Eq]: Eq[ZipNonEmptyList[A]] = Eq.by(_.value) diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 4f8bdc8d13..6883484ccf 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -350,6 +350,8 @@ object NonEmptyVector extends NonEmptyVectorInstances { def pure[A](x: A): ZipNonEmptyVector[A] = new ZipNonEmptyVector(NonEmptyVector.one(x)) def ap[A, B](ff: ZipNonEmptyVector[A => B])(fa: ZipNonEmptyVector[A]): ZipNonEmptyVector[B] = new ZipNonEmptyVector(ff.value.zipWith(fa.value)(_ apply _)) + override def product[A, B](fa: ZipNonEmptyVector[A], fb: ZipNonEmptyVector[B]): ZipNonEmptyVector[(A, B)] = + new ZipNonEmptyVector(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) } implicit def zipNevEq[A: Eq]: Eq[ZipNonEmptyVector[A]] = Eq.by(_.value) diff --git a/core/src/main/scala/cats/data/ZipVector.scala b/core/src/main/scala/cats/data/ZipVector.scala index 27de56bd3d..13ae82289b 100644 --- a/core/src/main/scala/cats/data/ZipVector.scala +++ b/core/src/main/scala/cats/data/ZipVector.scala @@ -10,6 +10,9 @@ object ZipVector { def pure[A](x: A): ZipVector[A] = new ZipVector(Vector(x)) def ap[A, B](ff: ZipVector[A => B])(fa: ZipVector[A]): ZipVector[B] = new ZipVector((ff.value, fa.value).zipped.map(_ apply _)) + + override def product[A, B](fa: ZipVector[A], fb: ZipVector[B]): ZipVector[(A, B)] = + new ZipVector(fa.value.zip(fb.value)) } implicit def catsDataEqForZipVector[A: Eq]: Eq[ZipVector[A]] = Eq.by(_.value) From 61b7cc706c9257165469bd82f28906dfd7c82f7c Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 21 Sep 2017 21:53:09 +0200 Subject: [PATCH 37/63] Add FailFastFuture + Parallel instance --- .../main/scala/cats/data/FailFastFuture.scala | 35 ++++++++++++++ .../main/scala/cats/instances/future.scala | 5 -- .../main/scala/cats/instances/parallel.scala | 15 ++++++ .../test/scala/cats/tests/FutureTests.scala | 11 ++++- .../test/scala/cats/tests/FutureTests.scala | 47 ++++++++++++++++++- 5 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 core/src/main/scala/cats/data/FailFastFuture.scala diff --git a/core/src/main/scala/cats/data/FailFastFuture.scala b/core/src/main/scala/cats/data/FailFastFuture.scala new file mode 100644 index 0000000000..0237174025 --- /dev/null +++ b/core/src/main/scala/cats/data/FailFastFuture.scala @@ -0,0 +1,35 @@ +package cats.data + +import cats.Applicative + +import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.util.{Failure, Success} + +class FailFastFuture[A](val value: Future[A]) extends AnyVal + +object FailFastFuture { + + def apply[A](value: Future[A]): FailFastFuture[A] = new FailFastFuture(value) + + def catsDataApplicativeForFailFastFuture(implicit ec: ExecutionContext): Applicative[FailFastFuture] = + new Applicative[FailFastFuture] { + override def pure[A](x: A): FailFastFuture[A] = FailFastFuture(Future.successful(x)) + + override def ap[A, B](ff: FailFastFuture[(A) => B])(fa: FailFastFuture[A]): FailFastFuture[B] = { + val p = Promise[B]() + + ff.value.onComplete { + case Failure(t) => p.tryFailure(t) + case Success(_) => () + } + + fa.value.onComplete { + case Failure(t) => p.tryFailure(t) + case Success(_) => () + } + + p.tryCompleteWith(ff.value.flatMap(f => fa.value.map(f))) + FailFastFuture(p.future) + } + } +} diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index f8c6458142..2c62956816 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -35,11 +35,6 @@ trait FutureInstances extends FutureInstances1 { private[instances] sealed trait FutureInstances1 extends FutureInstances2 { implicit def catsStdMonoidForFuture[A: Monoid](implicit ec: ExecutionContext): Monoid[Future[A]] = new FutureMonoid[A] - - implicit def catsStdParallelForFuture[A](implicit ec: ExecutionContext): Parallel[Future, Future] = { - implicit val M: Monad[Future] = cats.instances.future.catsStdInstancesForFuture - Parallel.identity[Future] - } } private[instances] sealed trait FutureInstances2 { diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 24889e73f1..103d520003 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -5,6 +5,7 @@ import cats.kernel.Semigroup import cats.syntax.either._ import cats.{Applicative, Functor, Monad, Parallel, ~>} +import scala.concurrent.{ExecutionContext, Future} trait ParallelInstances extends ParallelInstances1 { implicit def catsParallelForEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { @@ -50,6 +51,20 @@ trait ParallelInstances extends ParallelInstances1 { λ[Vector ~> ZipVector](v => new ZipVector(v)) } + implicit def catsStdParallelForFailFastFuture[A](implicit ec: ExecutionContext): Parallel[Future, FailFastFuture] = + new Parallel[Future, FailFastFuture] { + + def monad: Monad[Future] = cats.instances.future.catsStdInstancesForFuture + def applicative: Applicative[FailFastFuture] = FailFastFuture.catsDataApplicativeForFailFastFuture + + def sequential: FailFastFuture ~> Future = + λ[FailFastFuture ~> Future](_.value) + + def parallel: Future ~> FailFastFuture = + λ[Future ~> FailFastFuture](f => FailFastFuture(f)) + } + + implicit def catsParallelForEitherTNestedParallelValidated[F[_], M[_], E: Semigroup] (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] = new Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] { diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index dbe045a000..0150c809ec 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -2,6 +2,7 @@ package cats package js package tests +import cats.data.FailFastFuture import cats.kernel.laws.GroupLaws import cats.laws.discipline._ import cats.js.instances.Await @@ -10,7 +11,6 @@ import cats.tests.{CatsSuite, ListWrapper} import scala.concurrent.Future import scala.concurrent.duration._ - import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Arbitrary.arbitrary import cats.laws.discipline.arbitrary._ @@ -37,6 +37,13 @@ class FutureTests extends CatsSuite { } } + implicit def eqffa[A: Eq]: Eq[FailFastFuture[A]] = + Eq.by(_.value) + + + implicit def failFastArbitrary[A: Arbitrary]: Arbitrary[FailFastFuture[A]] = + Arbitrary(implicitly[Arbitrary[Future[A]]].arbitrary.map(FailFastFuture.apply)) + implicit val throwableEq: Eq[Throwable] = Eq[String].on(_.toString) @@ -55,7 +62,7 @@ class FutureTests extends CatsSuite { checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) - checkAll("Parallel[Future, Future]", ParallelTests[Future, Future, Int].parallel) + checkAll("Parallel[Future, FailFastFuture]", ParallelTests[Future, FailFastFuture, Int].parallel) { implicit val F = ListWrapper.semigroup[Int] diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 1cc66c0bde..3a68eb0dae 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -2,18 +2,21 @@ package cats package jvm package tests +import cats.data.FailFastFuture import cats.kernel.laws.GroupLaws import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.tests.{CatsSuite, ListWrapper} -import scala.concurrent.{Await, Future} +import scala.concurrent.{Await, Future, blocking} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.rng.Seed +import scala.util.{Try, Failure} + class FutureTests extends CatsSuite { val timeout = 3.seconds @@ -28,9 +31,15 @@ class FutureTests extends CatsSuite { } } + implicit def eqffa[A: Eq]: Eq[FailFastFuture[A]] = + Eq.by(_.value) + implicit def cogen[A: Cogen]: Cogen[Future[A]] = Cogen[Future[A]] { (seed: Seed, t: Future[A]) => Cogen[A].perturb(seed, Await.result(t, timeout)) } + implicit def failFastArbitrary[A: Arbitrary]: Arbitrary[FailFastFuture[A]] = + Arbitrary(implicitly[Arbitrary[Future[A]]].arbitrary.map(FailFastFuture.apply)) + implicit val throwableEq: Eq[Throwable] = Eq[String].on(_.toString) @@ -38,10 +47,44 @@ class FutureTests extends CatsSuite { implicit val nonFatalArbitrary: Arbitrary[Throwable] = Arbitrary(arbitrary[Exception].map(identity)) + test("FailFastFuture should fail fast on right side") { + val exA = new Exception("A") + val exB = new Exception("B") + val fa: Future[Int] = Future { + blocking(Thread.sleep(500)) + throw exA + } + + val fb: Future[Int] = Future { + blocking(Thread.sleep(100)) + throw exB + } + + val fab: Future[Int] = (fa, fb).parMapN(_ + _) + Try(Await.result(fab, timeout)) should === (Failure(exB)) + } + + test("FailFastFuture should fail fast on left side") { + val exA = new Exception("A") + val exB = new Exception("B") + val fa: Future[Int] = Future { + blocking(Thread.sleep(100)) + throw exA + } + + val fb: Future[Int] = Future { + blocking(Thread.sleep(500)) + throw exB + } + + val fab: Future[Int] = (fa, fb).parMapN(_ + _) + Try(Await.result(fab, timeout)) should === (Failure(exA)) + } + checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) checkAll("Future", CoflatMapTests[Future].coflatMap[Int, Int, Int]) - checkAll("Parallel[Future, Future]", ParallelTests[Future, Future, Int].parallel) + checkAll("Parallel[Future, FailFastFuture]", ParallelTests[Future, FailFastFuture, Int].parallel) { implicit val F = ListWrapper.semigroup[Int] From 50c5732ef35390e8596bffe34a9e93c819d28965 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 22 Sep 2017 08:59:37 +0200 Subject: [PATCH 38/63] Shorten wait times --- jvm/src/test/scala/cats/tests/FutureTests.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 3a68eb0dae..4a9a5a025b 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -51,12 +51,12 @@ class FutureTests extends CatsSuite { val exA = new Exception("A") val exB = new Exception("B") val fa: Future[Int] = Future { - blocking(Thread.sleep(500)) + blocking(Thread.sleep(200)) throw exA } val fb: Future[Int] = Future { - blocking(Thread.sleep(100)) + blocking(Thread.sleep(1)) throw exB } @@ -68,12 +68,12 @@ class FutureTests extends CatsSuite { val exA = new Exception("A") val exB = new Exception("B") val fa: Future[Int] = Future { - blocking(Thread.sleep(100)) + blocking(Thread.sleep(1)) throw exA } val fb: Future[Int] = Future { - blocking(Thread.sleep(500)) + blocking(Thread.sleep(200)) throw exB } From c6618602f2b6876500bf84c8567f4664a39ac8a2 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 22 Sep 2017 15:05:24 +0200 Subject: [PATCH 39/63] Add ZipStream and OneAnd instance --- core/src/main/scala/cats/data/OneAnd.scala | 40 +++++++++++++++++-- core/src/main/scala/cats/data/ZipStream.scala | 27 +++++++++++++ core/src/main/scala/cats/data/ZipVector.scala | 18 ++++++--- .../main/scala/cats/instances/parallel.scala | 15 ++++++- .../cats/laws/discipline/Arbitrary.scala | 3 ++ .../test/scala/cats/tests/ParallelTests.scala | 4 ++ 6 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 core/src/main/scala/cats/data/ZipStream.scala diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 43963d461a..55610ca2c4 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -98,7 +98,22 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { s"OneAnd(${A.show(head)}, ${FA.show(tail)})" } -private[data] sealed trait OneAndInstances extends OneAndLowPriority3 { +private[data] sealed trait OneAndInstances extends OneAndLowPriority4 { + + implicit def catsDataParallelForOneAnd[A, M[_]: Alternative, F[_]: Alternative] + (implicit P: Parallel[M, F]): Parallel[OneAnd[M, ?], OneAnd[F, ?]] = + new Parallel[OneAnd[M, ?], OneAnd[F, ?]] { + def monad: Monad[OneAnd[M, ?]] = catsDataMonadForOneAnd(P.monad, Alternative[M]) + + def applicative: Applicative[OneAnd[F, ?]] = catsDataApplicativeForOneAnd(Alternative[F]) + + def sequential: OneAnd[F, ?] ~> OneAnd[M, ?] = + λ[OneAnd[F, ?] ~> OneAnd[M, ?]](ofa => OneAnd(ofa.head, P.sequential(ofa.tail))) + + def parallel: OneAnd[M, ?] ~> OneAnd[F, ?] = + λ[OneAnd[M, ?] ~> OneAnd[F, ?]](ofa => OneAnd(ofa.head, P.parallel(ofa.tail))) + + } implicit def catsDataEqForOneAnd[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] = new Eq[OneAnd[F, A]]{ @@ -200,6 +215,25 @@ private[data] trait OneAndLowPriority0 { } private[data] trait OneAndLowPriority1 extends OneAndLowPriority0 { + implicit def catsDataApplicativeForOneAnd[F[_]](implicit F: Alternative[F]): Applicative[OneAnd[F, ?]] = + new Applicative[OneAnd[F, ?]] { + override def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = + fa.map(f) + + def pure[A](x: A): OneAnd[F, A] = + OneAnd(x, F.empty) + + override def ap[A, B](ff: OneAnd[F, A => B])(fa: OneAnd[F, A]): OneAnd[F, B] = { + val (f, tf) = (ff.head, ff.tail) + val (a, ta) = (fa.head, fa.tail) + val fb = F.ap(tf)(F.combineK(F.pure(a), ta)) + OneAnd(f(a), F.combineK(F.map(ta)(f), fb)) + } + } + +} + +private[data] trait OneAndLowPriority2 extends OneAndLowPriority1 { implicit def catsDataFunctorForOneAnd[F[_]](implicit F: Functor[F]): Functor[OneAnd[F, ?]] = new Functor[OneAnd[F, ?]] { def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = @@ -208,7 +242,7 @@ private[data] trait OneAndLowPriority1 extends OneAndLowPriority0 { } -private[data] trait OneAndLowPriority2 extends OneAndLowPriority1 { +private[data] trait OneAndLowPriority3 extends OneAndLowPriority2 { implicit def catsDataTraverseForOneAnd[F[_]](implicit F: Traverse[F]): Traverse[OneAnd[F, ?]] = new Traverse[OneAnd[F, ?]] { def traverse[G[_], A, B](fa: OneAnd[F, A])(f: (A) => G[B])(implicit G: Applicative[G]): G[OneAnd[F, B]] = { @@ -225,7 +259,7 @@ private[data] trait OneAndLowPriority2 extends OneAndLowPriority1 { } } -private[data] trait OneAndLowPriority3 extends OneAndLowPriority2 { +private[data] trait OneAndLowPriority4 extends OneAndLowPriority3 { implicit def catsDataNonEmptyTraverseForOneAnd[F[_]](implicit F: Traverse[F], F2: Alternative[F]): NonEmptyTraverse[OneAnd[F, ?]] = new NonEmptyReducible[OneAnd[F, ?], F] with NonEmptyTraverse[OneAnd[F, ?]] { def nonEmptyTraverse[G[_], A, B](fa: OneAnd[F, A])(f: (A) => G[B])(implicit G: Apply[G]): G[OneAnd[F, B]] = { diff --git a/core/src/main/scala/cats/data/ZipStream.scala b/core/src/main/scala/cats/data/ZipStream.scala new file mode 100644 index 0000000000..fce930aa3e --- /dev/null +++ b/core/src/main/scala/cats/data/ZipStream.scala @@ -0,0 +1,27 @@ +package cats.data + +import cats.{Alternative, Eq} +import cats.instances.stream._ + +class ZipStream[A](val value: Stream[A]) extends AnyVal + +object ZipStream { + + def apply[A](value: Stream[A]): ZipStream[A] = new ZipStream(value) + + implicit val catsDataAlternativeForZipStream: Alternative[ZipStream] = new Alternative[ZipStream] { + def pure[A](x: A): ZipStream[A] = new ZipStream(Stream(x)) + def ap[A, B](ff: ZipStream[A => B])(fa: ZipStream[A]): ZipStream[B] = + ZipStream((ff.value, fa.value).zipped.map(_ apply _)) + + override def product[A, B](fa: ZipStream[A], fb: ZipStream[B]): ZipStream[(A, B)] = + ZipStream(fa.value.zip(fb.value)) + + def empty[A]: ZipStream[A] = ZipStream(Stream.empty[A]) + + def combineK[A](x: ZipStream[A], y: ZipStream[A]): ZipStream[A] = + ZipStream(Alternative[Stream].combineK(x.value, y.value)) + } + + implicit def catsDataEqForZipStream[A: Eq]: Eq[ZipStream[A]] = Eq.by(_.value) +} diff --git a/core/src/main/scala/cats/data/ZipVector.scala b/core/src/main/scala/cats/data/ZipVector.scala index 13ae82289b..291b1a6fda 100644 --- a/core/src/main/scala/cats/data/ZipVector.scala +++ b/core/src/main/scala/cats/data/ZipVector.scala @@ -1,18 +1,26 @@ package cats.data -import cats.{Applicative, Eq} -import cats.kernel.instances.vector._ +import cats.{Alternative, Eq} +import cats.instances.vector._ class ZipVector[A](val value: Vector[A]) extends AnyVal object ZipVector { - implicit val catsDataApplicativeForZipVector: Applicative[ZipVector] = new Applicative[ZipVector] { + + def apply[A](value: Vector[A]): ZipVector[A] = new ZipVector(value) + + implicit val catsDataAlternativeForZipVector: Alternative[ZipVector] = new Alternative[ZipVector] { def pure[A](x: A): ZipVector[A] = new ZipVector(Vector(x)) def ap[A, B](ff: ZipVector[A => B])(fa: ZipVector[A]): ZipVector[B] = - new ZipVector((ff.value, fa.value).zipped.map(_ apply _)) + ZipVector((ff.value, fa.value).zipped.map(_ apply _)) override def product[A, B](fa: ZipVector[A], fb: ZipVector[B]): ZipVector[(A, B)] = - new ZipVector(fa.value.zip(fb.value)) + ZipVector(fa.value.zip(fb.value)) + + def empty[A]: ZipVector[A] = ZipVector(Vector.empty[A]) + + def combineK[A](x: ZipVector[A], y: ZipVector[A]): ZipVector[A] = + ZipVector(Alternative[Vector].combineK(x.value, y.value)) } implicit def catsDataEqForZipVector[A: Eq]: Eq[ZipVector[A]] = Eq.by(_.value) diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 103d520003..892b1755a8 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -42,7 +42,7 @@ trait ParallelInstances extends ParallelInstances1 { new Parallel[Vector, ZipVector] { def monad: Monad[Vector] = cats.instances.vector.catsStdInstancesForVector - def applicative: Applicative[ZipVector] = ZipVector.catsDataApplicativeForZipVector + def applicative: Applicative[ZipVector] = ZipVector.catsDataAlternativeForZipVector def sequential: ZipVector ~> Vector = λ[ZipVector ~> Vector](_.value) @@ -51,6 +51,19 @@ trait ParallelInstances extends ParallelInstances1 { λ[Vector ~> ZipVector](v => new ZipVector(v)) } + implicit def catsStdParallelForZipStream[A]: Parallel[Stream, ZipStream] = + new Parallel[Stream, ZipStream] { + + def monad: Monad[Stream] = cats.instances.stream.catsStdInstancesForStream + def applicative: Applicative[ZipStream] = ZipStream.catsDataAlternativeForZipStream + + def sequential: ZipStream ~> Stream = + λ[ZipStream ~> Stream](_.value) + + def parallel: Stream ~> ZipStream = + λ[Stream ~> ZipStream](v => new ZipStream(v)) + } + implicit def catsStdParallelForFailFastFuture[A](implicit ec: ExecutionContext): Parallel[Future, FailFastFuture] = new Parallel[Future, FailFastFuture] { diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 1bcdab47fb..bfbd8ec39e 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -53,6 +53,9 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForZipVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipVector[A]] = Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.map(v => new ZipVector(v))) + implicit def catsLawsArbitraryForZipStream[A](implicit A: Arbitrary[A]): Arbitrary[ZipStream[A]] = + Arbitrary(implicitly[Arbitrary[Stream[A]]].arbitrary.map(v => new ZipStream(v))) + implicit def catsLawsArbitraryForZipNonEmptyVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipNonEmptyVector[A]] = Arbitrary(implicitly[Arbitrary[NonEmptyVector[A]]].arbitrary.map(nev => new ZipNonEmptyVector(nev))) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index c67d6354fb..71e0804f09 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -90,8 +90,12 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?], Int].parallel) checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) checkAll("Parallel[Vector, ZipVector]", ParallelTypeclassTests[Vector, ZipVector, Int].parallel) + checkAll("Parallel[Stream, ZipStream]", ParallelTypeclassTests[Stream, ZipStream, Int].parallel) checkAll("Parallel[NonEmptyVector, ZipNonEmptyVector]", ParallelTypeclassTests[NonEmptyVector, ZipNonEmptyVector, Int].parallel) checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", ParallelTypeclassTests[NonEmptyList, ZipNonEmptyList, Int].parallel) + checkAll("Parallel[NonEmptyStream, OneAnd[ZipStream, ?]", ParallelTypeclassTests[NonEmptyStream, OneAnd[ZipStream, ?], Int].parallel) + checkAll("Parallel[OneAnd[Vector, ?], OneAnd[ZipVector, ?]", ParallelTypeclassTests[OneAnd[Vector, ?], OneAnd[ZipVector, ?], Int].parallel) + checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id, Int].parallel) From ccc5f45b264a36fd8c1783359d4ca06df1288b51 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 22 Sep 2017 15:07:38 +0200 Subject: [PATCH 40/63] Convert traits to abstract classes --- core/src/main/scala/cats/data/OneAnd.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 55610ca2c4..7386d7b1f1 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -98,7 +98,7 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { s"OneAnd(${A.show(head)}, ${FA.show(tail)})" } -private[data] sealed trait OneAndInstances extends OneAndLowPriority4 { +private[data] sealed abstract class OneAndInstances extends OneAndLowPriority4 { implicit def catsDataParallelForOneAnd[A, M[_]: Alternative, F[_]: Alternative] (implicit P: Parallel[M, F]): Parallel[OneAnd[M, ?], OneAnd[F, ?]] = @@ -193,7 +193,7 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority4 { } } -private[data] trait OneAndLowPriority0 { +private[data] sealed abstract class OneAndLowPriority0 { implicit val catsDataComonadForNonEmptyStream: Comonad[OneAnd[Stream, ?]] = new Comonad[OneAnd[Stream, ?]] { def coflatMap[A, B](fa: OneAnd[Stream, A])(f: OneAnd[Stream, A] => B): OneAnd[Stream, B] = { @@ -214,7 +214,7 @@ private[data] trait OneAndLowPriority0 { } } -private[data] trait OneAndLowPriority1 extends OneAndLowPriority0 { +private[data] sealed abstract class OneAndLowPriority1 extends OneAndLowPriority0 { implicit def catsDataApplicativeForOneAnd[F[_]](implicit F: Alternative[F]): Applicative[OneAnd[F, ?]] = new Applicative[OneAnd[F, ?]] { override def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = @@ -233,7 +233,7 @@ private[data] trait OneAndLowPriority1 extends OneAndLowPriority0 { } -private[data] trait OneAndLowPriority2 extends OneAndLowPriority1 { +private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority1 { implicit def catsDataFunctorForOneAnd[F[_]](implicit F: Functor[F]): Functor[OneAnd[F, ?]] = new Functor[OneAnd[F, ?]] { def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = @@ -242,7 +242,7 @@ private[data] trait OneAndLowPriority2 extends OneAndLowPriority1 { } -private[data] trait OneAndLowPriority3 extends OneAndLowPriority2 { +private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority2 { implicit def catsDataTraverseForOneAnd[F[_]](implicit F: Traverse[F]): Traverse[OneAnd[F, ?]] = new Traverse[OneAnd[F, ?]] { def traverse[G[_], A, B](fa: OneAnd[F, A])(f: (A) => G[B])(implicit G: Applicative[G]): G[OneAnd[F, B]] = { @@ -259,7 +259,7 @@ private[data] trait OneAndLowPriority3 extends OneAndLowPriority2 { } } -private[data] trait OneAndLowPriority4 extends OneAndLowPriority3 { +private[data] sealed abstract class OneAndLowPriority4 extends OneAndLowPriority3 { implicit def catsDataNonEmptyTraverseForOneAnd[F[_]](implicit F: Traverse[F], F2: Alternative[F]): NonEmptyTraverse[OneAnd[F, ?]] = new NonEmptyReducible[OneAnd[F, ?], F] with NonEmptyTraverse[OneAnd[F, ?]] { def nonEmptyTraverse[G[_], A, B](fa: OneAnd[F, A])(f: (A) => G[B])(implicit G: Apply[G]): G[OneAnd[F, B]] = { From ecc8e502f5587376d58e17c706f1bcebc3f20e10 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 22 Sep 2017 15:22:46 +0200 Subject: [PATCH 41/63] Add consistency test for zip stream --- tests/src/test/scala/cats/tests/ParallelTests.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 71e0804f09..c4e739a24c 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -84,6 +84,18 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } } + test("ParMap over Stream should be consistent with zip") { + forAll { (as: Stream[Int], bs: Stream[Int], cs: Stream[Int]) => + val zipped = as.zip(bs).map { + case (a, b) => a + b + }.zip(cs).map { + case (a, b) => a + b + } + + (as, bs, cs).parMapN(_ + _ + _) should === (zipped) + } + } + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) From 488240112003f6a0032e97b50a8dff3e0ac3974c Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 22 Sep 2017 15:40:03 +0200 Subject: [PATCH 42/63] Add Applicative test for Applicative[OneAnd] --- tests/src/test/scala/cats/tests/OneAndTests.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index bd0cd52359..c569b28659 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -5,7 +5,7 @@ import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.instances.stream._ import cats.data.{NonEmptyStream, OneAnd} -import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, NonEmptyTraverseTests, ReducibleTests} +import cats.laws.discipline.{ApplicativeTests, ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, NonEmptyTraverseTests, ReducibleTests} import cats.laws.discipline.arbitrary._ class OneAndTests extends CatsSuite { @@ -37,6 +37,12 @@ class OneAndTests extends CatsSuite { checkAll("MonadTests[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Monad[OneAnd[ListWrapper, ?]])) } + { + implicit val alternative = ListWrapper.alternative + checkAll("OneAnd[ListWrapper, Int]", ApplicativeTests[OneAnd[ListWrapper, ?]].applicative[Int, Int, Int]) + checkAll("Applicative[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Applicative[OneAnd[ListWrapper, ?]])) + } + { implicit val functor = ListWrapper.functor checkAll("OneAnd[ListWrapper, Int]", FunctorTests[OneAnd[ListWrapper, ?]].functor[Int, Int, Int]) From 50c9619bcc80bf13938f996d38853b381f35f1d0 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 27 Sep 2017 14:10:44 +0200 Subject: [PATCH 43/63] Add parAp test --- tests/src/test/scala/cats/tests/ParallelTests.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index c4e739a24c..e52018f48f 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -37,6 +37,11 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } } + test("parAp accumulates errors in order") { + val right: Either[String, Int => Int] = Left("Hello") + Parallel.parAp(right)("World".asLeft) should === (Left("HelloWorld")) + } + test("parAp2 accumulates errors in order") { val plus = (_: Int) + (_: Int) val rightPlus: Either[String, (Int, Int) => Int] = Right(plus) From db973c97d3579114eb80650642891163710b784f Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 28 Sep 2017 23:28:06 +0200 Subject: [PATCH 44/63] Add ZipList and lawtest all Zip* instances --- .../main/scala/cats/data/NonEmptyList.scala | 16 ++++++++--- .../main/scala/cats/data/NonEmptyVector.scala | 14 +++++++--- core/src/main/scala/cats/data/ZipList.scala | 27 +++++++++++++++++++ core/src/main/scala/cats/data/ZipStream.scala | 4 +++ core/src/main/scala/cats/data/ZipVector.scala | 16 +++++------ .../main/scala/cats/instances/parallel.scala | 15 ++++++++++- .../cats/laws/discipline/Arbitrary.scala | 3 +++ .../src/test/scala/cats/tests/ListTests.scala | 6 +++-- .../scala/cats/tests/NonEmptyListTests.scala | 6 +++-- .../cats/tests/NonEmptyVectorTests.scala | 5 ++-- .../test/scala/cats/tests/ParallelTests.scala | 2 +- .../test/scala/cats/tests/StreamTests.scala | 6 ++++- .../test/scala/cats/tests/VectorTests.scala | 6 +++-- 13 files changed, 98 insertions(+), 28 deletions(-) create mode 100644 core/src/main/scala/cats/data/ZipList.scala diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 68fb5768c8..f52b866e2f 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -394,15 +394,23 @@ object NonEmptyList extends NonEmptyListInstances { class ZipNonEmptyList[A](val value: NonEmptyList[A]) extends AnyVal object ZipNonEmptyList { + + def apply[A](nev: NonEmptyList[A]): ZipNonEmptyList[A] = + new ZipNonEmptyList(nev) + implicit val zipNelApplicative: Applicative[ZipNonEmptyList] = new Applicative[ZipNonEmptyList] { - def pure[A](x: A): ZipNonEmptyList[A] = new ZipNonEmptyList(NonEmptyList.one(x)) + def pure[A](x: A): ZipNonEmptyList[A] = ZipNonEmptyList(NonEmptyList.one(x)) def ap[A, B](ff: ZipNonEmptyList[A => B])(fa: ZipNonEmptyList[A]): ZipNonEmptyList[B] = - new ZipNonEmptyList(ff.value.zipWith(fa.value)(_ apply _)) + ZipNonEmptyList(ff.value.zipWith(fa.value)(_ apply _)) + + override def map[A, B](fa: ZipNonEmptyList[A])(f: (A) => B): ZipNonEmptyList[B] = + ZipNonEmptyList(fa.value.map(f)) + override def product[A, B](fa: ZipNonEmptyList[A], fb: ZipNonEmptyList[B]): ZipNonEmptyList[(A, B)] = - new ZipNonEmptyList(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) + ZipNonEmptyList(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) } - implicit def zipNelEq[A: Eq]: Eq[ZipNonEmptyList[A]] = Eq.by(_.value) + implicit def zipNevEq[A: Eq]: Eq[ZipNonEmptyList[A]] = Eq.by(_.value) } } diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 29bf53f769..2a8d80d987 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -359,12 +359,20 @@ object NonEmptyVector extends NonEmptyVectorInstances with Serializable { class ZipNonEmptyVector[A](val value: NonEmptyVector[A]) extends Serializable object ZipNonEmptyVector { + + def apply[A](nev: NonEmptyVector[A]): ZipNonEmptyVector[A] = + new ZipNonEmptyVector(nev) + implicit val zipNevApplicative: Applicative[ZipNonEmptyVector] = new Applicative[ZipNonEmptyVector] { - def pure[A](x: A): ZipNonEmptyVector[A] = new ZipNonEmptyVector(NonEmptyVector.one(x)) + def pure[A](x: A): ZipNonEmptyVector[A] = ZipNonEmptyVector(NonEmptyVector.one(x)) def ap[A, B](ff: ZipNonEmptyVector[A => B])(fa: ZipNonEmptyVector[A]): ZipNonEmptyVector[B] = - new ZipNonEmptyVector(ff.value.zipWith(fa.value)(_ apply _)) + ZipNonEmptyVector(ff.value.zipWith(fa.value)(_ apply _)) + + override def map[A, B](fa: ZipNonEmptyVector[A])(f: (A) => B): ZipNonEmptyVector[B] = + ZipNonEmptyVector(fa.value.map(f)) + override def product[A, B](fa: ZipNonEmptyVector[A], fb: ZipNonEmptyVector[B]): ZipNonEmptyVector[(A, B)] = - new ZipNonEmptyVector(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) + ZipNonEmptyVector(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) } implicit def zipNevEq[A: Eq]: Eq[ZipNonEmptyVector[A]] = Eq.by(_.value) diff --git a/core/src/main/scala/cats/data/ZipList.scala b/core/src/main/scala/cats/data/ZipList.scala new file mode 100644 index 0000000000..98038d2871 --- /dev/null +++ b/core/src/main/scala/cats/data/ZipList.scala @@ -0,0 +1,27 @@ +package cats.data + +import cats.{Applicative, Eq} +import cats.instances.list.catsKernelStdEqForList + +class ZipList[A](val value: List[A]) extends AnyVal + +object ZipList { + + def apply[A](value: List[A]): ZipList[A] = new ZipList(value) + + implicit val catsDataApplicativeForZipList: Applicative[ZipList] = new Applicative[ZipList] { + def pure[A](x: A): ZipList[A] = new ZipList(List(x)) + + override def map[A, B](fa: ZipList[A])(f: (A) => B): ZipList[B] = + ZipList(fa.value.map(f)) + + def ap[A, B](ff: ZipList[A => B])(fa: ZipList[A]): ZipList[B] = + ZipList((ff.value, fa.value).zipped.map(_ apply _)) + + override def product[A, B](fa: ZipList[A], fb: ZipList[B]): ZipList[(A, B)] = + ZipList(fa.value.zip(fb.value)) + + } + + implicit def catsDataEqForZipList[A: Eq]: Eq[ZipList[A]] = Eq.by(_.value) +} diff --git a/core/src/main/scala/cats/data/ZipStream.scala b/core/src/main/scala/cats/data/ZipStream.scala index fce930aa3e..23edb23fd8 100644 --- a/core/src/main/scala/cats/data/ZipStream.scala +++ b/core/src/main/scala/cats/data/ZipStream.scala @@ -11,6 +11,10 @@ object ZipStream { implicit val catsDataAlternativeForZipStream: Alternative[ZipStream] = new Alternative[ZipStream] { def pure[A](x: A): ZipStream[A] = new ZipStream(Stream(x)) + + override def map[A, B](fa: ZipStream[A])(f: (A) => B): ZipStream[B] = + ZipStream(fa.value.map(f)) + def ap[A, B](ff: ZipStream[A => B])(fa: ZipStream[A]): ZipStream[B] = ZipStream((ff.value, fa.value).zipped.map(_ apply _)) diff --git a/core/src/main/scala/cats/data/ZipVector.scala b/core/src/main/scala/cats/data/ZipVector.scala index 291b1a6fda..b98540cc99 100644 --- a/core/src/main/scala/cats/data/ZipVector.scala +++ b/core/src/main/scala/cats/data/ZipVector.scala @@ -1,6 +1,6 @@ package cats.data -import cats.{Alternative, Eq} +import cats.{Applicative, Eq} import cats.instances.vector._ class ZipVector[A](val value: Vector[A]) extends AnyVal @@ -9,18 +9,14 @@ object ZipVector { def apply[A](value: Vector[A]): ZipVector[A] = new ZipVector(value) - implicit val catsDataAlternativeForZipVector: Alternative[ZipVector] = new Alternative[ZipVector] { - def pure[A](x: A): ZipVector[A] = new ZipVector(Vector(x)) + implicit val catsDataApplicativeForZipVector: Applicative[ZipVector] = new Applicative[ZipVector] { + def pure[A](x: A): ZipVector[A] = ZipVector(Vector(x)) + + override def map[A, B](fa: ZipVector[A])(f: (A) => B): ZipVector[B] = + ZipVector(fa.value.map(f)) def ap[A, B](ff: ZipVector[A => B])(fa: ZipVector[A]): ZipVector[B] = ZipVector((ff.value, fa.value).zipped.map(_ apply _)) - override def product[A, B](fa: ZipVector[A], fb: ZipVector[B]): ZipVector[(A, B)] = - ZipVector(fa.value.zip(fb.value)) - - def empty[A]: ZipVector[A] = ZipVector(Vector.empty[A]) - - def combineK[A](x: ZipVector[A], y: ZipVector[A]): ZipVector[A] = - ZipVector(Alternative[Vector].combineK(x.value, y.value)) } implicit def catsDataEqForZipVector[A: Eq]: Eq[ZipVector[A]] = Eq.by(_.value) diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 892b1755a8..c1003cee75 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -38,11 +38,24 @@ trait ParallelInstances extends ParallelInstances1 { λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel(optT.value))) } + implicit def catsStdParallelForZipList[A]: Parallel[List, ZipList] = + new Parallel[List, ZipList] { + + def monad: Monad[List] = cats.instances.list.catsStdInstancesForList + def applicative: Applicative[ZipList] = ZipList.catsDataApplicativeForZipList + + def sequential: ZipList ~> List = + λ[ZipList ~> List](_.value) + + def parallel: List ~> ZipList = + λ[List ~> ZipList](v => new ZipList(v)) + } + implicit def catsStdParallelForZipVector[A]: Parallel[Vector, ZipVector] = new Parallel[Vector, ZipVector] { def monad: Monad[Vector] = cats.instances.vector.catsStdInstancesForVector - def applicative: Applicative[ZipVector] = ZipVector.catsDataAlternativeForZipVector + def applicative: Applicative[ZipVector] = ZipVector.catsDataApplicativeForZipVector def sequential: ZipVector ~> Vector = λ[ZipVector ~> Vector](_.value) diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 923de216c4..cda217fe76 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -53,6 +53,9 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForZipVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipVector[A]] = Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.map(v => new ZipVector(v))) + implicit def catsLawsArbitraryForZipList[A](implicit A: Arbitrary[A]): Arbitrary[ZipList[A]] = + Arbitrary(implicitly[Arbitrary[List[A]]].arbitrary.map(v => new ZipList(v))) + implicit def catsLawsArbitraryForZipStream[A](implicit A: Arbitrary[A]): Arbitrary[ZipStream[A]] = Arbitrary(implicitly[Arbitrary[Stream[A]]].arbitrary.map(v => new ZipStream(v))) diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index bb8640a296..e37794d0dc 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -1,8 +1,8 @@ package cats package tests -import cats.data.NonEmptyList -import cats.laws.discipline.{TraverseTests, CoflatMapTests, AlternativeTests, SerializableTests, CartesianTests} +import cats.data.{NonEmptyList, ZipList} +import cats.laws.discipline.{ApplyTests, TraverseTests, CoflatMapTests, AlternativeTests, SerializableTests, CartesianTests} import cats.laws.discipline.arbitrary._ class ListTests extends CatsSuite { @@ -19,6 +19,8 @@ class ListTests extends CatsSuite { checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) + checkAll("ZipList[Int]", ApplyTests[ZipList].apply[Int, Int, Int]) + test("nel => list => nel returns original nel")( forAll { fa: NonEmptyList[Int] => fa.toList.toNel should === (Some(fa)) diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index 55261d2aa5..e4ab424e2b 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -2,10 +2,10 @@ package cats package tests import cats.kernel.laws.{GroupLaws, OrderLaws} - +import cats.data.NonEmptyList.ZipNonEmptyList import cats.data.{NonEmptyList, NonEmptyVector} import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{ComonadTests, NonEmptyTraverseTests, MonadTests, ReducibleTests, SemigroupKTests, SerializableTests} +import cats.laws.discipline.{ApplyTests, ComonadTests, NonEmptyTraverseTests, MonadTests, ReducibleTests, SemigroupKTests, SerializableTests} class NonEmptyListTests extends CatsSuite { // Lots of collections here.. telling ScalaCheck to calm down a bit @@ -35,6 +35,8 @@ class NonEmptyListTests extends CatsSuite { checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].eqv) checkAll("Eq[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(Eq[NonEmptyList[ListWrapper[Int]]])) + checkAll("ZipNonEmptyList[Int]", ApplyTests[ZipNonEmptyList].apply[Int, Int, Int]) + { implicit val A = ListWrapper.partialOrder[Int] checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].partialOrder) diff --git a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala index 3b271e1b2f..64f393ac1b 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala @@ -4,9 +4,9 @@ package tests import catalysts.Platform import cats.kernel.laws.{GroupLaws, OrderLaws} - +import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data.NonEmptyVector -import cats.laws.discipline.{ComonadTests, SemigroupKTests, FoldableTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests, MonadTests} +import cats.laws.discipline.{ApplyTests, ComonadTests, SemigroupKTests, FoldableTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests, MonadTests} import cats.laws.discipline.arbitrary._ import scala.util.Properties @@ -53,6 +53,7 @@ class NonEmptyVectorTests extends CatsSuite { checkAll("NonEmptyVector[Int]", MonadTests[NonEmptyVector].monad[Int, Int, Int]) checkAll("Monad[NonEmptyVector]", SerializableTests.serializable(Monad[NonEmptyVector])) + checkAll("ZipNonEmptyVector[Int]", ApplyTests[ZipNonEmptyVector].apply[Int, Int, Int]) test("size is consistent with toList.size") { forAll { (nonEmptyVector: NonEmptyVector[Int]) => diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index e52018f48f..94e86b76ed 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -107,11 +107,11 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?], Int].parallel) checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) checkAll("Parallel[Vector, ZipVector]", ParallelTypeclassTests[Vector, ZipVector, Int].parallel) + checkAll("Parallel[List, ZipList]", ParallelTypeclassTests[List, ZipList, Int].parallel) checkAll("Parallel[Stream, ZipStream]", ParallelTypeclassTests[Stream, ZipStream, Int].parallel) checkAll("Parallel[NonEmptyVector, ZipNonEmptyVector]", ParallelTypeclassTests[NonEmptyVector, ZipNonEmptyVector, Int].parallel) checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", ParallelTypeclassTests[NonEmptyList, ZipNonEmptyList, Int].parallel) checkAll("Parallel[NonEmptyStream, OneAnd[ZipStream, ?]", ParallelTypeclassTests[NonEmptyStream, OneAnd[ZipStream, ?], Int].parallel) - checkAll("Parallel[OneAnd[Vector, ?], OneAnd[ZipVector, ?]", ParallelTypeclassTests[OneAnd[Vector, ?], OneAnd[ZipVector, ?], Int].parallel) checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id, Int].parallel) diff --git a/tests/src/test/scala/cats/tests/StreamTests.scala b/tests/src/test/scala/cats/tests/StreamTests.scala index 8ea90f21e9..87b0194e65 100644 --- a/tests/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/src/test/scala/cats/tests/StreamTests.scala @@ -1,7 +1,9 @@ package cats package tests -import cats.laws.discipline.{CoflatMapTests, MonadTests, AlternativeTests, SerializableTests, TraverseTests, CartesianTests} +import cats.data.ZipStream +import cats.laws.discipline.{AlternativeTests, ApplyTests, CartesianTests, CoflatMapTests, MonadTests, SerializableTests, TraverseTests} +import cats.laws.discipline.arbitrary._ class StreamTests extends CatsSuite { checkAll("Stream[Int]", CartesianTests[Stream].cartesian[Int, Int, Int]) @@ -19,6 +21,8 @@ class StreamTests extends CatsSuite { checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) + checkAll("ZipStream[Int]", ApplyTests[ZipStream].apply[Int, Int, Int]) + test("show") { Stream(1, 2, 3).show should === ("Stream(1, ?)") Stream.empty[Int].show should === ("Stream()") diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala index d1251f9414..f9e6d130e1 100644 --- a/tests/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/src/test/scala/cats/tests/VectorTests.scala @@ -1,8 +1,8 @@ package cats package tests -import cats.data.NonEmptyVector -import cats.laws.discipline.{AlternativeTests, CoflatMapTests, SerializableTests, TraverseTests, CartesianTests} +import cats.data.{NonEmptyVector, ZipVector} +import cats.laws.discipline.{AlternativeTests, ApplyTests, CoflatMapTests, SerializableTests, TraverseTests, CartesianTests} import cats.laws.discipline.arbitrary._ class VectorTests extends CatsSuite { @@ -18,6 +18,8 @@ class VectorTests extends CatsSuite { checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector])) + checkAll("ZipVector[Int]", ApplyTests[ZipVector].apply[Int, Int, Int]) + test("show") { Vector(1, 2, 3).show should === ("Vector(1, 2, 3)") From 615a1a594bac7ea9bc87fd7e6bb963d7c2eb6ecc Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 28 Sep 2017 23:58:38 +0200 Subject: [PATCH 45/63] Add ZipList consistency test --- tests/src/test/scala/cats/tests/ParallelTests.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 94e86b76ed..32e143365e 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -77,6 +77,18 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } } + test("ParMap over List should be consistent with zip") { + forAll { (as: List[Int], bs: List[Int], cs: List[Int]) => + val zipped = as.zip(bs).map { + case (a, b) => a + b + }.zip(cs).map { + case (a, b) => a + b + } + + (as, bs, cs).parMapN(_ + _ + _) should === (zipped) + } + } + test("ParMap over Vector should be consistent with zip") { forAll { (as: Vector[Int], bs: Vector[Int], cs: Vector[Int]) => val zipped = as.zip(bs).map { From 7395c0a163067b2c1a840888c39d0e5f3390b651 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 29 Sep 2017 01:51:46 +0200 Subject: [PATCH 46/63] Add NonEmptyParallel --- core/src/main/scala/cats/Parallel.scala | 83 +++++++++++++++++-- .../main/scala/cats/data/NonEmptyList.scala | 13 ++- .../main/scala/cats/data/NonEmptyVector.scala | 11 ++- core/src/main/scala/cats/data/ZipList.scala | 5 +- core/src/main/scala/cats/data/ZipStream.scala | 2 +- core/src/main/scala/cats/data/ZipVector.scala | 5 +- .../main/scala/cats/instances/parallel.scala | 18 ++-- .../cats/laws/NonEmptyParallelLaws.scala | 21 +++++ .../main/scala/cats/laws/ParallelLaws.scala | 12 +-- .../discipline/NonEmptyParallelTests.scala | 25 ++++++ .../cats/laws/discipline/ParallelTests.scala | 9 +- project/Boilerplate.scala | 14 ++-- .../test/scala/cats/tests/ParallelTests.scala | 16 ++-- .../test/scala/cats/tests/StreamTests.scala | 1 + 14 files changed, 171 insertions(+), 64 deletions(-) create mode 100644 laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 3a0fe413fe..2dd0d9c959 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -6,7 +6,34 @@ import cats.arrow.FunctionK * Some types that form a Monad, are also capable of forming an Applicative that supports parallel composition. * The Parallel type class allows us to represent this relationship. */ -trait Parallel[M[_], F[_]] extends Serializable { +trait NonEmptyParallel[M[_], F[_]] extends Serializable { + /** + * The applicative instance for F[_] + */ + def applicative: Apply[F] + + /** + * The monad instance for M[_] + */ + def monad: FlatMap[M] + + /** + * Natural Transformation from the parallel Applicative F[_] to the sequential Monad M[_]. + */ + def sequential: F ~> M + + /** + * Natural Transformation from the sequential Monad M[_] to the parallel Applicative F[_]. + */ + def parallel: M ~> F + +} + +/** + * Some types that form a Monad, are also capable of forming an Applicative that supports parallel composition. + * The Parallel type class allows us to represent this relationship. + */ +trait Parallel[M[_], F[_]] extends NonEmptyParallel[M, F] { /** * The applicative instance for F[_] */ @@ -49,6 +76,10 @@ trait Parallel[M[_], F[_]] extends Serializable { } } +object NonEmptyParallel { + def apply[M[_], F[_]](implicit P: NonEmptyParallel[M, F]): NonEmptyParallel[M, F] = P +} + object Parallel extends ParallelArityFunctions { def apply[M[_], F[_]](implicit P: Parallel[M, F]): Parallel[M, F] = P @@ -93,13 +124,53 @@ object Parallel extends ParallelArityFunctions { P.sequential(gtb) } + /** + * Like `NonEmptyTraverse[A].nonEmptySequence`, but uses the apply instance + * corresponding to the Parallel instance instead. + */ + def parNonEmptySequence[T[_]: NonEmptyTraverse, M[_], F[_], A] + (tma: T[M[A]])(implicit P: NonEmptyParallel[M, F]): M[T[A]] = { + val fta: F[T[A]] = NonEmptyTraverse[T].nonEmptyTraverse(tma)(P.parallel.apply)(P.applicative) + P.sequential(fta) + } + + /** + * Like `NonEmptyTraverse[A].nonEmptyTraverse`, but uses the apply instance + * corresponding to the Parallel instance instead. + */ + def parNonEmptyTraverse[T[_]: NonEmptyTraverse, M[_], F[_], A, B] + (ta: T[A])(f: A => M[B])(implicit P: NonEmptyParallel[M, F]): M[T[B]] = { + val gtb: F[T[B]] = NonEmptyTraverse[T].nonEmptyTraverse(ta)(f andThen P.parallel.apply)(P.applicative) + P.sequential(gtb) + } + + /** + * Like `Reducible[A].nonEmptySequence_`, but uses the apply instance + * corresponding to the Parallel instance instead. + */ + def parNonEmptySequence_[T[_]: Reducible, M[_], F[_], A] + (tma: T[M[A]])(implicit P: NonEmptyParallel[M, F]): M[Unit] = { + val fu: F[Unit] = Reducible[T].nonEmptyTraverse_(tma)(P.parallel.apply)(P.applicative) + P.sequential(fu) + } + + /** + * Like `Reducible[A].nonEmptyTraverse_`, but uses the apply instance + * corresponding to the Parallel instance instead. + */ + def parNonEmptyTraverse_[T[_]: Reducible, M[_], F[_], A, B] + (ta: T[A])(f: A => M[B])(implicit P: NonEmptyParallel[M, F]): M[Unit] = { + val gtb: F[Unit] = Reducible[T].nonEmptyTraverse_(ta)(f andThen P.parallel.apply)(P.applicative) + P.sequential(gtb) + } + /** * Like `Applicative[F].ap`, but uses the applicative instance * corresponding to the Parallel instance instead. */ def parAp[M[_], F[_], A, B](mf: M[A => B]) - (ma: M[A]) - (implicit P: Parallel[M, F]): M[B] = + (ma: M[A]) + (implicit P: NonEmptyParallel[M, F]): M[B] = P.sequential(P.applicative.ap(P.parallel(mf))(P.parallel(ma))) /** @@ -107,7 +178,7 @@ object Parallel extends ParallelArityFunctions { * corresponding to the Parallel instance instead. */ def parProduct[M[_], F[_], A, B](ma: M[A], mb: M[B]) - (implicit P: Parallel[M, F]): M[(A, B)] = + (implicit P: NonEmptyParallel[M, F]): M[(A, B)] = P.sequential(P.applicative.product(P.parallel(ma), P.parallel(mb))) /** @@ -115,8 +186,8 @@ object Parallel extends ParallelArityFunctions { * corresponding to the Parallel instance instead. */ def parAp2[M[_], F[_], A, B, Z](ff: M[(A, B) => Z]) - (ma: M[A], mb: M[B]) - (implicit P: Parallel[M, F]): M[Z] = + (ma: M[A], mb: M[B]) + (implicit P: NonEmptyParallel[M, F]): M[Z] = P.sequential( P.applicative.ap2(P.parallel(ff))(P.parallel(ma), P.parallel(mb)) ) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index f52b866e2f..ebae692fff 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -398,8 +398,7 @@ object NonEmptyList extends NonEmptyListInstances { def apply[A](nev: NonEmptyList[A]): ZipNonEmptyList[A] = new ZipNonEmptyList(nev) - implicit val zipNelApplicative: Applicative[ZipNonEmptyList] = new Applicative[ZipNonEmptyList] { - def pure[A](x: A): ZipNonEmptyList[A] = ZipNonEmptyList(NonEmptyList.one(x)) + implicit val zipNelApply: Apply[ZipNonEmptyList] = new Apply[ZipNonEmptyList] { def ap[A, B](ff: ZipNonEmptyList[A => B])(fa: ZipNonEmptyList[A]): ZipNonEmptyList[B] = ZipNonEmptyList(ff.value.zipWith(fa.value)(_ apply _)) @@ -410,7 +409,7 @@ object NonEmptyList extends NonEmptyListInstances { ZipNonEmptyList(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) } - implicit def zipNevEq[A: Eq]: Eq[ZipNonEmptyList[A]] = Eq.by(_.value) + implicit def zipNelEq[A: Eq]: Eq[ZipNonEmptyList[A]] = Eq.by(_.value) } } @@ -526,12 +525,12 @@ private[data] sealed abstract class NonEmptyListInstances extends NonEmptyListIn val A0 = A } - implicit def catsDataParallelForNonEmptyList[A]: Parallel[NonEmptyList, ZipNonEmptyList] = - new Parallel[NonEmptyList, ZipNonEmptyList] { + implicit def catsDataNonEmptyParallelForNonEmptyList[A]: NonEmptyParallel[NonEmptyList, ZipNonEmptyList] = + new NonEmptyParallel[NonEmptyList, ZipNonEmptyList] { - def monad: Monad[NonEmptyList] = NonEmptyList.catsDataInstancesForNonEmptyList + def monad: FlatMap[NonEmptyList] = NonEmptyList.catsDataInstancesForNonEmptyList - def applicative: Applicative[ZipNonEmptyList] = ZipNonEmptyList.zipNelApplicative + def applicative: Apply[ZipNonEmptyList] = ZipNonEmptyList.zipNelApply def sequential: ZipNonEmptyList ~> NonEmptyList = λ[ZipNonEmptyList ~> NonEmptyList](_.value) diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 2a8d80d987..bde499fe8b 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -318,11 +318,11 @@ private[data] sealed abstract class NonEmptyVectorInstances { implicit def catsDataSemigroupForNonEmptyVector[A]: Semigroup[NonEmptyVector[A]] = catsDataInstancesForNonEmptyVector.algebra - implicit def catsDataParallelForNonEmptyVector[A]: Parallel[NonEmptyVector, ZipNonEmptyVector] = - new Parallel[NonEmptyVector, ZipNonEmptyVector] { + implicit def catsDataParallelForNonEmptyVector[A]: NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector] = + new NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector] { - def applicative: Applicative[ZipNonEmptyVector] = ZipNonEmptyVector.zipNevApplicative - def monad: Monad[NonEmptyVector] = NonEmptyVector.catsDataInstancesForNonEmptyVector + def applicative: Apply[ZipNonEmptyVector] = ZipNonEmptyVector.zipNevApply + def monad: FlatMap[NonEmptyVector] = NonEmptyVector.catsDataInstancesForNonEmptyVector def sequential: ZipNonEmptyVector ~> NonEmptyVector = λ[ZipNonEmptyVector ~> NonEmptyVector](_.value) @@ -363,8 +363,7 @@ object NonEmptyVector extends NonEmptyVectorInstances with Serializable { def apply[A](nev: NonEmptyVector[A]): ZipNonEmptyVector[A] = new ZipNonEmptyVector(nev) - implicit val zipNevApplicative: Applicative[ZipNonEmptyVector] = new Applicative[ZipNonEmptyVector] { - def pure[A](x: A): ZipNonEmptyVector[A] = ZipNonEmptyVector(NonEmptyVector.one(x)) + implicit val zipNevApply: Apply[ZipNonEmptyVector] = new Apply[ZipNonEmptyVector] { def ap[A, B](ff: ZipNonEmptyVector[A => B])(fa: ZipNonEmptyVector[A]): ZipNonEmptyVector[B] = ZipNonEmptyVector(ff.value.zipWith(fa.value)(_ apply _)) diff --git a/core/src/main/scala/cats/data/ZipList.scala b/core/src/main/scala/cats/data/ZipList.scala index 98038d2871..e123c77843 100644 --- a/core/src/main/scala/cats/data/ZipList.scala +++ b/core/src/main/scala/cats/data/ZipList.scala @@ -1,6 +1,6 @@ package cats.data -import cats.{Applicative, Eq} +import cats.{Apply, Eq} import cats.instances.list.catsKernelStdEqForList class ZipList[A](val value: List[A]) extends AnyVal @@ -9,8 +9,7 @@ object ZipList { def apply[A](value: List[A]): ZipList[A] = new ZipList(value) - implicit val catsDataApplicativeForZipList: Applicative[ZipList] = new Applicative[ZipList] { - def pure[A](x: A): ZipList[A] = new ZipList(List(x)) + implicit val catsDataApplyForZipList: Apply[ZipList] = new Apply[ZipList] { override def map[A, B](fa: ZipList[A])(f: (A) => B): ZipList[B] = ZipList(fa.value.map(f)) diff --git a/core/src/main/scala/cats/data/ZipStream.scala b/core/src/main/scala/cats/data/ZipStream.scala index 23edb23fd8..a89ab02099 100644 --- a/core/src/main/scala/cats/data/ZipStream.scala +++ b/core/src/main/scala/cats/data/ZipStream.scala @@ -10,7 +10,7 @@ object ZipStream { def apply[A](value: Stream[A]): ZipStream[A] = new ZipStream(value) implicit val catsDataAlternativeForZipStream: Alternative[ZipStream] = new Alternative[ZipStream] { - def pure[A](x: A): ZipStream[A] = new ZipStream(Stream(x)) + def pure[A](x: A): ZipStream[A] = new ZipStream(Stream.continually(x)) override def map[A, B](fa: ZipStream[A])(f: (A) => B): ZipStream[B] = ZipStream(fa.value.map(f)) diff --git a/core/src/main/scala/cats/data/ZipVector.scala b/core/src/main/scala/cats/data/ZipVector.scala index b98540cc99..cce9641b3e 100644 --- a/core/src/main/scala/cats/data/ZipVector.scala +++ b/core/src/main/scala/cats/data/ZipVector.scala @@ -1,6 +1,6 @@ package cats.data -import cats.{Applicative, Eq} +import cats.{Apply, Eq} import cats.instances.vector._ class ZipVector[A](val value: Vector[A]) extends AnyVal @@ -9,8 +9,7 @@ object ZipVector { def apply[A](value: Vector[A]): ZipVector[A] = new ZipVector(value) - implicit val catsDataApplicativeForZipVector: Applicative[ZipVector] = new Applicative[ZipVector] { - def pure[A](x: A): ZipVector[A] = ZipVector(Vector(x)) + implicit val catsDataApplyForZipVector: Apply[ZipVector] = new Apply[ZipVector] { override def map[A, B](fa: ZipVector[A])(f: (A) => B): ZipVector[B] = ZipVector(fa.value.map(f)) diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index c1003cee75..1d4636e7ee 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -3,7 +3,7 @@ package cats.instances import cats.data._ import cats.kernel.Semigroup import cats.syntax.either._ -import cats.{Applicative, Functor, Monad, Parallel, ~>} +import cats.{Applicative, Apply, FlatMap, Functor, Monad, NonEmptyParallel, Parallel, ~>} import scala.concurrent.{ExecutionContext, Future} @@ -38,11 +38,11 @@ trait ParallelInstances extends ParallelInstances1 { λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel(optT.value))) } - implicit def catsStdParallelForZipList[A]: Parallel[List, ZipList] = - new Parallel[List, ZipList] { + implicit def catsStdNonEmptyParallelForZipList[A]: NonEmptyParallel[List, ZipList] = + new NonEmptyParallel[List, ZipList] { - def monad: Monad[List] = cats.instances.list.catsStdInstancesForList - def applicative: Applicative[ZipList] = ZipList.catsDataApplicativeForZipList + def monad: FlatMap[List] = cats.instances.list.catsStdInstancesForList + def applicative: Apply[ZipList] = ZipList.catsDataApplyForZipList def sequential: ZipList ~> List = λ[ZipList ~> List](_.value) @@ -51,11 +51,11 @@ trait ParallelInstances extends ParallelInstances1 { λ[List ~> ZipList](v => new ZipList(v)) } - implicit def catsStdParallelForZipVector[A]: Parallel[Vector, ZipVector] = - new Parallel[Vector, ZipVector] { + implicit def catsStdNonEmptyParallelForZipVector[A]: NonEmptyParallel[Vector, ZipVector] = + new NonEmptyParallel[Vector, ZipVector] { - def monad: Monad[Vector] = cats.instances.vector.catsStdInstancesForVector - def applicative: Applicative[ZipVector] = ZipVector.catsDataApplicativeForZipVector + def monad: FlatMap[Vector] = cats.instances.vector.catsStdInstancesForVector + def applicative: Apply[ZipVector] = ZipVector.catsDataApplyForZipVector def sequential: ZipVector ~> Vector = λ[ZipVector ~> Vector](_.value) diff --git a/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala b/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala new file mode 100644 index 0000000000..94ddb59198 --- /dev/null +++ b/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala @@ -0,0 +1,21 @@ +package cats +package laws + + +/** + * Laws that must be obeyed by any `cats.NonEmptyParallel`. + */ +trait NonEmptyParallelLaws[M[_], F[_]] { + def P: NonEmptyParallel[M, F] + + def parallelRoundTrip[A](ma: M[A]): IsEq[M[A]] = + P.sequential(P.parallel(ma)) <-> ma + + def sequentialRoundTrip[A](fa: F[A]): IsEq[F[A]] = + P.parallel(P.sequential(fa)) <-> fa +} + +object NonEmptyParallelLaws { + def apply[M[_], F[_]](implicit ev: NonEmptyParallel[M, F]): NonEmptyParallelLaws[M, F] = + new NonEmptyParallelLaws[M, F] { def P: NonEmptyParallel[M, F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/ParallelLaws.scala b/laws/src/main/scala/cats/laws/ParallelLaws.scala index f99a300203..d750064cc0 100644 --- a/laws/src/main/scala/cats/laws/ParallelLaws.scala +++ b/laws/src/main/scala/cats/laws/ParallelLaws.scala @@ -5,20 +5,14 @@ package laws /** * Laws that must be obeyed by any `cats.Parallel`. */ -trait ParallelLaws[M[_], F[_]] { +trait ParallelLaws[M[_], F[_]] extends NonEmptyParallelLaws[M, F] { def P: Parallel[M, F] - def parallelRoundTrip[A](ma: M[A]): IsEq[M[A]] = - P.sequential(P.parallel(ma)) <-> ma - - def sequentialRoundTrip[A](fa: F[A]): IsEq[F[A]] = - P.parallel(P.sequential(fa)) <-> fa - def isomorphicPure[A](a: A): IsEq[F[A]] = P.applicative.pure(a) <-> P.parallel(P.monad.pure(a)) } object ParallelLaws { - def apply[M[_]: Monad, F[_]](implicit ev: Parallel[M, F]): ParallelLaws[M, F] = - new ParallelLaws[M, F] { def P: Parallel[M, F] = ev; def monadM: Monad[M] = Monad[M] } + def apply[M[_], F[_]](implicit ev: Parallel[M, F]): ParallelLaws[M, F] = + new ParallelLaws[M, F] { def P: Parallel[M, F] = ev } } diff --git a/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala new file mode 100644 index 0000000000..369b0a0c4b --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala @@ -0,0 +1,25 @@ +package cats +package laws +package discipline + +import org.scalacheck.Arbitrary +import org.scalacheck.Prop.forAll +import org.typelevel.discipline.Laws + +trait NonEmptyParallelTests[M[_], F[_], A] extends Laws { + def laws: NonEmptyParallelLaws[M, F] + + def nonEmptyParallel + (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = + new DefaultRuleSet( + "parallel", + None, + "parallel round trip" -> forAll((ma: M[A]) => laws.parallelRoundTrip(ma)), + "sequential round trip" -> forAll((fa: F[A]) => laws.sequentialRoundTrip(fa)) + ) +} + +object NonEmptyParallelTests { + def apply[M[_], F[_], A](implicit ev: NonEmptyParallel[M, F]): NonEmptyParallelTests[M, F, A] = + new NonEmptyParallelTests[M, F, A] { val laws: NonEmptyParallelLaws[M, F] = NonEmptyParallelLaws[M, F] } +} diff --git a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala index ace7d90de5..b5df4102be 100644 --- a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala @@ -4,23 +4,20 @@ package discipline import org.scalacheck.Arbitrary import org.scalacheck.Prop.forAll -import org.typelevel.discipline.Laws -trait ParallelTests[M[_], F[_], A] extends Laws { +trait ParallelTests[M[_], F[_], A] extends NonEmptyParallelTests[M, F, A] { def laws: ParallelLaws[M, F] def parallel (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = new DefaultRuleSet( "parallel", - None, - "parallel round trip" -> forAll((ma: M[A]) => laws.parallelRoundTrip(ma)), - "sequential round trip" -> forAll((fa: F[A]) => laws.sequentialRoundTrip(fa)), + Some(nonEmptyParallel), "isomorphic pure" -> forAll((a: A) => laws.isomorphicPure(a)) ) } object ParallelTests { - def apply[M[_]: Monad, F[_], A](implicit ev: Parallel[M, F]): ParallelTests[M, F, A] = + def apply[M[_], F[_], A](implicit ev: Parallel[M, F]): ParallelTests[M, F, A] = new ParallelTests[M, F, A] { val laws: ParallelLaws[M, F] = ParallelLaws[M, F] } } diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index 66510cfe8b..9122512282 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -211,8 +211,8 @@ object Boilerplate { block""" |package cats |trait ParallelArityFunctions { - - def parMap$arity[M[_]: Monad, F[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit p: Parallel[M, F]): M[Z] = - - Monad[M].map($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } + - def parMap$arity[M[_], F[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] = + - p.monad.map($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } |} """ } @@ -264,21 +264,21 @@ object Boilerplate { val n = if (arity == 1) { "" } else { arity.toString } val parMap = - if (arity == 1) s"def parMap[F[_], Z](f: (${`A..N`}) => Z)(implicit p: Parallel[M, F]): M[Z] = Monad[M].map($tupleArgs)(f)" - else s"def parMapN[F[_], Z](f: (${`A..N`}) => Z)(implicit p: Parallel[M, F]): M[Z] = Parallel.parMap$arity($tupleArgs)(f)" + if (arity == 1) s"def parMap[F[_], Z](f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] = p.monad.map($tupleArgs)(f)" + else s"def parMapN[F[_], Z](f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] = Parallel.parMap$arity($tupleArgs)(f)" block""" |package cats |package syntax | - |import cats.{Monad, Parallel} + |import cats.Parallel | |trait TupleParallelSyntax { - - implicit def catsSyntaxTuple${arity}Parallel[M[_]: Monad, ${`A..N`}]($tupleTpe): Tuple${arity}ParallelOps[M, ${`A..N`}] = new Tuple${arity}ParallelOps(t$arity) + - implicit def catsSyntaxTuple${arity}Parallel[M[_], ${`A..N`}]($tupleTpe): Tuple${arity}ParallelOps[M, ${`A..N`}] = new Tuple${arity}ParallelOps(t$arity) |} | - -private[syntax] final class Tuple${arity}ParallelOps[M[_]: Monad, ${`A..N`}]($tupleTpe) { + -private[syntax] final class Tuple${arity}ParallelOps[M[_], ${`A..N`}]($tupleTpe) { - $parMap -} | diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 32e143365e..4b0229b7ec 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -6,7 +6,7 @@ import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data._ import cats.tests.CatsSuite import org.scalatest.FunSuite -import cats.laws.discipline.{ApplicativeErrorTests, SerializableTests, ParallelTests => ParallelTypeclassTests} +import cats.laws.discipline.{ApplicativeErrorTests, NonEmptyParallelTests, SerializableTests, ParallelTests => ParallelTypeclassTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -118,17 +118,19 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?], Int].parallel) checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) - checkAll("Parallel[Vector, ZipVector]", ParallelTypeclassTests[Vector, ZipVector, Int].parallel) - checkAll("Parallel[List, ZipList]", ParallelTypeclassTests[List, ZipList, Int].parallel) - checkAll("Parallel[Stream, ZipStream]", ParallelTypeclassTests[Stream, ZipStream, Int].parallel) - checkAll("Parallel[NonEmptyVector, ZipNonEmptyVector]", ParallelTypeclassTests[NonEmptyVector, ZipNonEmptyVector, Int].parallel) - checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", ParallelTypeclassTests[NonEmptyList, ZipNonEmptyList, Int].parallel) + checkAll("NonEmptyParallel[Vector, ZipVector]", NonEmptyParallelTests[Vector, ZipVector, Int].nonEmptyParallel) + checkAll("NonEmptyParallel[List, ZipList]", NonEmptyParallelTests[List, ZipList, Int].nonEmptyParallel) + // Can't test Parallel here, as Applicative[ZipStream].pure doesn't terminate + checkAll("Parallel[Stream, ZipStream]", NonEmptyParallelTests[Stream, ZipStream, Int].nonEmptyParallel) + checkAll("NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector]", NonEmptyParallelTests[NonEmptyVector, ZipNonEmptyVector, Int].nonEmptyParallel) + checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", NonEmptyParallelTests[NonEmptyList, ZipNonEmptyList, Int].nonEmptyParallel) checkAll("Parallel[NonEmptyStream, OneAnd[ZipStream, ?]", ParallelTypeclassTests[NonEmptyStream, OneAnd[ZipStream, ?], Int].parallel) checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id, Int].parallel) - checkAll("Parallel[NonEmptyList, ZipNonEmptyList]", SerializableTests.serializable(Parallel[NonEmptyList, ZipNonEmptyList])) + checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", SerializableTests.serializable(NonEmptyParallel[NonEmptyList, ZipNonEmptyList])) + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", SerializableTests.serializable(Parallel[Either[String, ?], Validated[String, ?]])) { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = diff --git a/tests/src/test/scala/cats/tests/StreamTests.scala b/tests/src/test/scala/cats/tests/StreamTests.scala index 87b0194e65..f0414e3c43 100644 --- a/tests/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/src/test/scala/cats/tests/StreamTests.scala @@ -21,6 +21,7 @@ class StreamTests extends CatsSuite { checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) + // Can't test applicative laws as they don't terminate checkAll("ZipStream[Int]", ApplyTests[ZipStream].apply[Int, Int, Int]) test("show") { From a8afdfec52756c7c64127fa515cd336884b88eb9 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 29 Sep 2017 08:53:26 +0200 Subject: [PATCH 47/63] Add test cases for ParNonEmptyTraverse --- tests/src/test/scala/cats/tests/ParallelTests.scala | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 4b0229b7ec..39b3b9bcc1 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -37,6 +37,18 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } } + test("ParNonEmptyTraverse identity should be equivalent to parNonEmptySequence") { + forAll { es: NonEmptyVector[List[Int]] => + (Parallel.parNonEmptyTraverse(es)(identity)) should === (Parallel.parNonEmptySequence(es)) + } + } + + test("ParNonEmptyTraverse_ identity should be equivalent to parNonEmptySequence_") { + forAll { es: NonEmptyList[List[Int]] => + (Parallel.parNonEmptyTraverse_(es)(identity)) should === (Parallel.parNonEmptySequence_(es)) + } + } + test("parAp accumulates errors in order") { val right: Either[String, Int => Int] = Left("Hello") Parallel.parAp(right)("World".asLeft) should === (Left("HelloWorld")) From 7f9107262bf38130a363c81c390d9b7d71c618d6 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 29 Sep 2017 08:57:09 +0200 Subject: [PATCH 48/63] Update scaladoc --- core/src/main/scala/cats/Parallel.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 2dd0d9c959..90cadd5a52 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -3,27 +3,27 @@ package cats import cats.arrow.FunctionK /** - * Some types that form a Monad, are also capable of forming an Applicative that supports parallel composition. - * The Parallel type class allows us to represent this relationship. + * Some types that form a FlatMap, are also capable of forming an Apply that supports parallel composition. + * The NonEmptyParallel type class allows us to represent this relationship. */ trait NonEmptyParallel[M[_], F[_]] extends Serializable { /** - * The applicative instance for F[_] + * The Apply instance for F[_] */ def applicative: Apply[F] /** - * The monad instance for M[_] + * The FlatMap instance for M[_] */ def monad: FlatMap[M] /** - * Natural Transformation from the parallel Applicative F[_] to the sequential Monad M[_]. + * Natural Transformation from the parallel Apply F[_] to the sequential FlatMap M[_]. */ def sequential: F ~> M /** - * Natural Transformation from the sequential Monad M[_] to the parallel Applicative F[_]. + * Natural Transformation from the sequential FlatMap M[_] to the parallel Apply F[_]. */ def parallel: M ~> F From c5e34236ff0ecde627c6104a96b30d632f8ce03e Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 29 Sep 2017 16:20:56 +0200 Subject: [PATCH 49/63] Remove FailFastFuture and all Zip* instances --- .../main/scala/cats/data/FailFastFuture.scala | 35 ---------- .../main/scala/cats/data/NonEmptyList.scala | 35 ---------- .../main/scala/cats/data/NonEmptyVector.scala | 32 ---------- core/src/main/scala/cats/data/OneAnd.scala | 42 ++---------- core/src/main/scala/cats/data/ZipList.scala | 26 -------- core/src/main/scala/cats/data/ZipStream.scala | 31 --------- core/src/main/scala/cats/data/ZipVector.scala | 22 ------- .../main/scala/cats/instances/parallel.scala | 56 +--------------- .../test/scala/cats/tests/FutureTests.scala | 10 +-- .../test/scala/cats/tests/FutureTests.scala | 46 +------------ .../cats/laws/discipline/Arbitrary.scala | 16 ----- .../src/test/scala/cats/tests/ListTests.scala | 5 +- .../scala/cats/tests/NonEmptyListTests.scala | 4 +- .../cats/tests/NonEmptyVectorTests.scala | 5 +- .../test/scala/cats/tests/OneAndTests.scala | 8 +-- .../test/scala/cats/tests/ParallelTests.scala | 64 +------------------ .../test/scala/cats/tests/StreamTests.scala | 7 +- .../test/scala/cats/tests/VectorTests.scala | 6 +- 18 files changed, 18 insertions(+), 432 deletions(-) delete mode 100644 core/src/main/scala/cats/data/FailFastFuture.scala delete mode 100644 core/src/main/scala/cats/data/ZipList.scala delete mode 100644 core/src/main/scala/cats/data/ZipStream.scala delete mode 100644 core/src/main/scala/cats/data/ZipVector.scala diff --git a/core/src/main/scala/cats/data/FailFastFuture.scala b/core/src/main/scala/cats/data/FailFastFuture.scala deleted file mode 100644 index 0237174025..0000000000 --- a/core/src/main/scala/cats/data/FailFastFuture.scala +++ /dev/null @@ -1,35 +0,0 @@ -package cats.data - -import cats.Applicative - -import scala.concurrent.{ExecutionContext, Future, Promise} -import scala.util.{Failure, Success} - -class FailFastFuture[A](val value: Future[A]) extends AnyVal - -object FailFastFuture { - - def apply[A](value: Future[A]): FailFastFuture[A] = new FailFastFuture(value) - - def catsDataApplicativeForFailFastFuture(implicit ec: ExecutionContext): Applicative[FailFastFuture] = - new Applicative[FailFastFuture] { - override def pure[A](x: A): FailFastFuture[A] = FailFastFuture(Future.successful(x)) - - override def ap[A, B](ff: FailFastFuture[(A) => B])(fa: FailFastFuture[A]): FailFastFuture[B] = { - val p = Promise[B]() - - ff.value.onComplete { - case Failure(t) => p.tryFailure(t) - case Success(_) => () - } - - fa.value.onComplete { - case Failure(t) => p.tryFailure(t) - case Success(_) => () - } - - p.tryCompleteWith(ff.value.flatMap(f => fa.value.map(f))) - FailFastFuture(p.future) - } - } -} diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index ebae692fff..9e3cd4f089 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -1,7 +1,6 @@ package cats package data -import cats.data.NonEmptyList.ZipNonEmptyList import cats.instances.list._ import cats.syntax.order._ @@ -391,26 +390,6 @@ object NonEmptyList extends NonEmptyListInstances { def fromReducible[F[_], A](fa: F[A])(implicit F: Reducible[F]): NonEmptyList[A] = F.toNonEmptyList(fa) - class ZipNonEmptyList[A](val value: NonEmptyList[A]) extends AnyVal - - object ZipNonEmptyList { - - def apply[A](nev: NonEmptyList[A]): ZipNonEmptyList[A] = - new ZipNonEmptyList(nev) - - implicit val zipNelApply: Apply[ZipNonEmptyList] = new Apply[ZipNonEmptyList] { - def ap[A, B](ff: ZipNonEmptyList[A => B])(fa: ZipNonEmptyList[A]): ZipNonEmptyList[B] = - ZipNonEmptyList(ff.value.zipWith(fa.value)(_ apply _)) - - override def map[A, B](fa: ZipNonEmptyList[A])(f: (A) => B): ZipNonEmptyList[B] = - ZipNonEmptyList(fa.value.map(f)) - - override def product[A, B](fa: ZipNonEmptyList[A], fb: ZipNonEmptyList[B]): ZipNonEmptyList[(A, B)] = - ZipNonEmptyList(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) - } - - implicit def zipNelEq[A: Eq]: Eq[ZipNonEmptyList[A]] = Eq.by(_.value) - } } private[data] sealed abstract class NonEmptyListInstances extends NonEmptyListInstances0 { @@ -524,20 +503,6 @@ private[data] sealed abstract class NonEmptyListInstances extends NonEmptyListIn new NonEmptyListOrder[A] { val A0 = A } - - implicit def catsDataNonEmptyParallelForNonEmptyList[A]: NonEmptyParallel[NonEmptyList, ZipNonEmptyList] = - new NonEmptyParallel[NonEmptyList, ZipNonEmptyList] { - - def monad: FlatMap[NonEmptyList] = NonEmptyList.catsDataInstancesForNonEmptyList - - def applicative: Apply[ZipNonEmptyList] = ZipNonEmptyList.zipNelApply - - def sequential: ZipNonEmptyList ~> NonEmptyList = - λ[ZipNonEmptyList ~> NonEmptyList](_.value) - - def parallel: NonEmptyList ~> ZipNonEmptyList = - λ[NonEmptyList ~> ZipNonEmptyList](nel => new ZipNonEmptyList(nel)) - } } private[data] sealed abstract class NonEmptyListInstances0 extends NonEmptyListInstances1 { diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index bde499fe8b..b37db559ae 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -1,7 +1,6 @@ package cats package data -import cats.data.NonEmptyVector.ZipNonEmptyVector import scala.annotation.tailrec import scala.collection.immutable.{TreeSet, VectorBuilder} import cats.instances.vector._ @@ -318,18 +317,6 @@ private[data] sealed abstract class NonEmptyVectorInstances { implicit def catsDataSemigroupForNonEmptyVector[A]: Semigroup[NonEmptyVector[A]] = catsDataInstancesForNonEmptyVector.algebra - implicit def catsDataParallelForNonEmptyVector[A]: NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector] = - new NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector] { - - def applicative: Apply[ZipNonEmptyVector] = ZipNonEmptyVector.zipNevApply - def monad: FlatMap[NonEmptyVector] = NonEmptyVector.catsDataInstancesForNonEmptyVector - - def sequential: ZipNonEmptyVector ~> NonEmptyVector = - λ[ZipNonEmptyVector ~> NonEmptyVector](_.value) - - def parallel: NonEmptyVector ~> ZipNonEmptyVector = - λ[NonEmptyVector ~> ZipNonEmptyVector](nev => new ZipNonEmptyVector(nev)) - } } @@ -356,24 +343,5 @@ object NonEmptyVector extends NonEmptyVectorInstances with Serializable { if (vector.nonEmpty) new NonEmptyVector(vector) else throw new IllegalArgumentException("Cannot create NonEmptyVector from empty vector") - class ZipNonEmptyVector[A](val value: NonEmptyVector[A]) extends Serializable - - object ZipNonEmptyVector { - - def apply[A](nev: NonEmptyVector[A]): ZipNonEmptyVector[A] = - new ZipNonEmptyVector(nev) - implicit val zipNevApply: Apply[ZipNonEmptyVector] = new Apply[ZipNonEmptyVector] { - def ap[A, B](ff: ZipNonEmptyVector[A => B])(fa: ZipNonEmptyVector[A]): ZipNonEmptyVector[B] = - ZipNonEmptyVector(ff.value.zipWith(fa.value)(_ apply _)) - - override def map[A, B](fa: ZipNonEmptyVector[A])(f: (A) => B): ZipNonEmptyVector[B] = - ZipNonEmptyVector(fa.value.map(f)) - - override def product[A, B](fa: ZipNonEmptyVector[A], fb: ZipNonEmptyVector[B]): ZipNonEmptyVector[(A, B)] = - ZipNonEmptyVector(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) - } - - implicit def zipNevEq[A: Eq]: Eq[ZipNonEmptyVector[A]] = Eq.by(_.value) - } } diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 7c5d0d2242..038b9eb719 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -99,22 +99,7 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { } -private[data] sealed abstract class OneAndInstances extends OneAndLowPriority4 { - - implicit def catsDataParallelForOneAnd[A, M[_]: Alternative, F[_]: Alternative] - (implicit P: Parallel[M, F]): Parallel[OneAnd[M, ?], OneAnd[F, ?]] = - new Parallel[OneAnd[M, ?], OneAnd[F, ?]] { - def monad: Monad[OneAnd[M, ?]] = catsDataMonadForOneAnd(P.monad, Alternative[M]) - - def applicative: Applicative[OneAnd[F, ?]] = catsDataApplicativeForOneAnd(Alternative[F]) - - def sequential: OneAnd[F, ?] ~> OneAnd[M, ?] = - λ[OneAnd[F, ?] ~> OneAnd[M, ?]](ofa => OneAnd(ofa.head, P.sequential(ofa.tail))) - - def parallel: OneAnd[M, ?] ~> OneAnd[F, ?] = - λ[OneAnd[M, ?] ~> OneAnd[F, ?]](ofa => OneAnd(ofa.head, P.parallel(ofa.tail))) - - } +private[data] sealed abstract class OneAndInstances extends OneAndLowPriority3 { implicit def catsDataEqForOneAnd[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] = @@ -216,27 +201,8 @@ private[data] sealed abstract class OneAndLowPriority0 { } } -private[data] sealed abstract class OneAndLowPriority1 extends OneAndLowPriority0 { - - implicit def catsDataApplicativeForOneAnd[F[_]](implicit F: Alternative[F]): Applicative[OneAnd[F, ?]] = - new Applicative[OneAnd[F, ?]] { - override def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = - fa.map(f) - - def pure[A](x: A): OneAnd[F, A] = - OneAnd(x, F.empty) - override def ap[A, B](ff: OneAnd[F, A => B])(fa: OneAnd[F, A]): OneAnd[F, B] = { - val (f, tf) = (ff.head, ff.tail) - val (a, ta) = (fa.head, fa.tail) - val fb = F.ap(tf)(F.combineK(F.pure(a), ta)) - OneAnd(f(a), F.combineK(F.map(ta)(f), fb)) - } - } - -} - -private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority1 { +private[data] sealed abstract class OneAndLowPriority1 extends OneAndLowPriority0 { implicit def catsDataFunctorForOneAnd[F[_]](implicit F: Functor[F]): Functor[OneAnd[F, ?]] = new Functor[OneAnd[F, ?]] { @@ -246,7 +212,7 @@ private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority } -private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority2 { +private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority1 { implicit def catsDataTraverseForOneAnd[F[_]](implicit F: Traverse[F]): Traverse[OneAnd[F, ?]] = new Traverse[OneAnd[F, ?]] { @@ -265,7 +231,7 @@ private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority } -private[data] sealed abstract class OneAndLowPriority4 extends OneAndLowPriority3 { +private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority2 { implicit def catsDataNonEmptyTraverseForOneAnd[F[_]](implicit F: Traverse[F], F2: Alternative[F]): NonEmptyTraverse[OneAnd[F, ?]] = new NonEmptyReducible[OneAnd[F, ?], F] with NonEmptyTraverse[OneAnd[F, ?]] { diff --git a/core/src/main/scala/cats/data/ZipList.scala b/core/src/main/scala/cats/data/ZipList.scala deleted file mode 100644 index e123c77843..0000000000 --- a/core/src/main/scala/cats/data/ZipList.scala +++ /dev/null @@ -1,26 +0,0 @@ -package cats.data - -import cats.{Apply, Eq} -import cats.instances.list.catsKernelStdEqForList - -class ZipList[A](val value: List[A]) extends AnyVal - -object ZipList { - - def apply[A](value: List[A]): ZipList[A] = new ZipList(value) - - implicit val catsDataApplyForZipList: Apply[ZipList] = new Apply[ZipList] { - - override def map[A, B](fa: ZipList[A])(f: (A) => B): ZipList[B] = - ZipList(fa.value.map(f)) - - def ap[A, B](ff: ZipList[A => B])(fa: ZipList[A]): ZipList[B] = - ZipList((ff.value, fa.value).zipped.map(_ apply _)) - - override def product[A, B](fa: ZipList[A], fb: ZipList[B]): ZipList[(A, B)] = - ZipList(fa.value.zip(fb.value)) - - } - - implicit def catsDataEqForZipList[A: Eq]: Eq[ZipList[A]] = Eq.by(_.value) -} diff --git a/core/src/main/scala/cats/data/ZipStream.scala b/core/src/main/scala/cats/data/ZipStream.scala deleted file mode 100644 index a89ab02099..0000000000 --- a/core/src/main/scala/cats/data/ZipStream.scala +++ /dev/null @@ -1,31 +0,0 @@ -package cats.data - -import cats.{Alternative, Eq} -import cats.instances.stream._ - -class ZipStream[A](val value: Stream[A]) extends AnyVal - -object ZipStream { - - def apply[A](value: Stream[A]): ZipStream[A] = new ZipStream(value) - - implicit val catsDataAlternativeForZipStream: Alternative[ZipStream] = new Alternative[ZipStream] { - def pure[A](x: A): ZipStream[A] = new ZipStream(Stream.continually(x)) - - override def map[A, B](fa: ZipStream[A])(f: (A) => B): ZipStream[B] = - ZipStream(fa.value.map(f)) - - def ap[A, B](ff: ZipStream[A => B])(fa: ZipStream[A]): ZipStream[B] = - ZipStream((ff.value, fa.value).zipped.map(_ apply _)) - - override def product[A, B](fa: ZipStream[A], fb: ZipStream[B]): ZipStream[(A, B)] = - ZipStream(fa.value.zip(fb.value)) - - def empty[A]: ZipStream[A] = ZipStream(Stream.empty[A]) - - def combineK[A](x: ZipStream[A], y: ZipStream[A]): ZipStream[A] = - ZipStream(Alternative[Stream].combineK(x.value, y.value)) - } - - implicit def catsDataEqForZipStream[A: Eq]: Eq[ZipStream[A]] = Eq.by(_.value) -} diff --git a/core/src/main/scala/cats/data/ZipVector.scala b/core/src/main/scala/cats/data/ZipVector.scala deleted file mode 100644 index cce9641b3e..0000000000 --- a/core/src/main/scala/cats/data/ZipVector.scala +++ /dev/null @@ -1,22 +0,0 @@ -package cats.data - -import cats.{Apply, Eq} -import cats.instances.vector._ - -class ZipVector[A](val value: Vector[A]) extends AnyVal - -object ZipVector { - - def apply[A](value: Vector[A]): ZipVector[A] = new ZipVector(value) - - implicit val catsDataApplyForZipVector: Apply[ZipVector] = new Apply[ZipVector] { - - override def map[A, B](fa: ZipVector[A])(f: (A) => B): ZipVector[B] = - ZipVector(fa.value.map(f)) - def ap[A, B](ff: ZipVector[A => B])(fa: ZipVector[A]): ZipVector[B] = - ZipVector((ff.value, fa.value).zipped.map(_ apply _)) - - } - - implicit def catsDataEqForZipVector[A: Eq]: Eq[ZipVector[A]] = Eq.by(_.value) -} diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 1d4636e7ee..c0baac289f 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -3,9 +3,7 @@ package cats.instances import cats.data._ import cats.kernel.Semigroup import cats.syntax.either._ -import cats.{Applicative, Apply, FlatMap, Functor, Monad, NonEmptyParallel, Parallel, ~>} - -import scala.concurrent.{ExecutionContext, Future} +import cats.{Applicative, Functor, Monad, Parallel, ~>} trait ParallelInstances extends ParallelInstances1 { implicit def catsParallelForEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { @@ -38,58 +36,6 @@ trait ParallelInstances extends ParallelInstances1 { λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel(optT.value))) } - implicit def catsStdNonEmptyParallelForZipList[A]: NonEmptyParallel[List, ZipList] = - new NonEmptyParallel[List, ZipList] { - - def monad: FlatMap[List] = cats.instances.list.catsStdInstancesForList - def applicative: Apply[ZipList] = ZipList.catsDataApplyForZipList - - def sequential: ZipList ~> List = - λ[ZipList ~> List](_.value) - - def parallel: List ~> ZipList = - λ[List ~> ZipList](v => new ZipList(v)) - } - - implicit def catsStdNonEmptyParallelForZipVector[A]: NonEmptyParallel[Vector, ZipVector] = - new NonEmptyParallel[Vector, ZipVector] { - - def monad: FlatMap[Vector] = cats.instances.vector.catsStdInstancesForVector - def applicative: Apply[ZipVector] = ZipVector.catsDataApplyForZipVector - - def sequential: ZipVector ~> Vector = - λ[ZipVector ~> Vector](_.value) - - def parallel: Vector ~> ZipVector = - λ[Vector ~> ZipVector](v => new ZipVector(v)) - } - - implicit def catsStdParallelForZipStream[A]: Parallel[Stream, ZipStream] = - new Parallel[Stream, ZipStream] { - - def monad: Monad[Stream] = cats.instances.stream.catsStdInstancesForStream - def applicative: Applicative[ZipStream] = ZipStream.catsDataAlternativeForZipStream - - def sequential: ZipStream ~> Stream = - λ[ZipStream ~> Stream](_.value) - - def parallel: Stream ~> ZipStream = - λ[Stream ~> ZipStream](v => new ZipStream(v)) - } - - implicit def catsStdParallelForFailFastFuture[A](implicit ec: ExecutionContext): Parallel[Future, FailFastFuture] = - new Parallel[Future, FailFastFuture] { - - def monad: Monad[Future] = cats.instances.future.catsStdInstancesForFuture - def applicative: Applicative[FailFastFuture] = FailFastFuture.catsDataApplicativeForFailFastFuture - - def sequential: FailFastFuture ~> Future = - λ[FailFastFuture ~> Future](_.value) - - def parallel: Future ~> FailFastFuture = - λ[Future ~> FailFastFuture](f => FailFastFuture(f)) - } - implicit def catsParallelForEitherTNestedParallelValidated[F[_], M[_], E: Semigroup] (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] = diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 0150c809ec..61f4c1010d 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -2,7 +2,6 @@ package cats package js package tests -import cats.data.FailFastFuture import cats.kernel.laws.GroupLaws import cats.laws.discipline._ import cats.js.instances.Await @@ -11,6 +10,7 @@ import cats.tests.{CatsSuite, ListWrapper} import scala.concurrent.Future import scala.concurrent.duration._ + import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Arbitrary.arbitrary import cats.laws.discipline.arbitrary._ @@ -37,13 +37,6 @@ class FutureTests extends CatsSuite { } } - implicit def eqffa[A: Eq]: Eq[FailFastFuture[A]] = - Eq.by(_.value) - - - implicit def failFastArbitrary[A: Arbitrary]: Arbitrary[FailFastFuture[A]] = - Arbitrary(implicitly[Arbitrary[Future[A]]].arbitrary.map(FailFastFuture.apply)) - implicit val throwableEq: Eq[Throwable] = Eq[String].on(_.toString) @@ -62,7 +55,6 @@ class FutureTests extends CatsSuite { checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) - checkAll("Parallel[Future, FailFastFuture]", ParallelTests[Future, FailFastFuture, Int].parallel) { implicit val F = ListWrapper.semigroup[Int] diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 4a9a5a025b..bf011071a8 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -2,21 +2,18 @@ package cats package jvm package tests -import cats.data.FailFastFuture import cats.kernel.laws.GroupLaws import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.tests.{CatsSuite, ListWrapper} -import scala.concurrent.{Await, Future, blocking} +import scala.concurrent.{Await, Future} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.rng.Seed -import scala.util.{Try, Failure} - class FutureTests extends CatsSuite { val timeout = 3.seconds @@ -31,15 +28,9 @@ class FutureTests extends CatsSuite { } } - implicit def eqffa[A: Eq]: Eq[FailFastFuture[A]] = - Eq.by(_.value) - implicit def cogen[A: Cogen]: Cogen[Future[A]] = Cogen[Future[A]] { (seed: Seed, t: Future[A]) => Cogen[A].perturb(seed, Await.result(t, timeout)) } - implicit def failFastArbitrary[A: Arbitrary]: Arbitrary[FailFastFuture[A]] = - Arbitrary(implicitly[Arbitrary[Future[A]]].arbitrary.map(FailFastFuture.apply)) - implicit val throwableEq: Eq[Throwable] = Eq[String].on(_.toString) @@ -47,44 +38,9 @@ class FutureTests extends CatsSuite { implicit val nonFatalArbitrary: Arbitrary[Throwable] = Arbitrary(arbitrary[Exception].map(identity)) - test("FailFastFuture should fail fast on right side") { - val exA = new Exception("A") - val exB = new Exception("B") - val fa: Future[Int] = Future { - blocking(Thread.sleep(200)) - throw exA - } - - val fb: Future[Int] = Future { - blocking(Thread.sleep(1)) - throw exB - } - - val fab: Future[Int] = (fa, fb).parMapN(_ + _) - Try(Await.result(fab, timeout)) should === (Failure(exB)) - } - - test("FailFastFuture should fail fast on left side") { - val exA = new Exception("A") - val exB = new Exception("B") - val fa: Future[Int] = Future { - blocking(Thread.sleep(1)) - throw exA - } - - val fb: Future[Int] = Future { - blocking(Thread.sleep(200)) - throw exB - } - - val fab: Future[Int] = (fa, fb).parMapN(_ + _) - Try(Await.result(fab, timeout)) should === (Failure(exA)) - } - checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) checkAll("Future", CoflatMapTests[Future].coflatMap[Int, Int, Int]) - checkAll("Parallel[Future, FailFastFuture]", ParallelTests[Future, FailFastFuture, Int].parallel) { implicit val F = ListWrapper.semigroup[Int] diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index cda217fe76..e1ecc1b02c 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -2,8 +2,6 @@ package cats package laws package discipline -import cats.data.NonEmptyList.ZipNonEmptyList -import cats.data.NonEmptyVector.ZipNonEmptyVector import scala.util.{Failure, Success, Try} import cats.data._ import org.scalacheck.{Arbitrary, Cogen, Gen} @@ -50,26 +48,12 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsCogenForNonEmptyVector[A](implicit A: Cogen[A]): Cogen[NonEmptyVector[A]] = Cogen[Vector[A]].contramap(_.toVector) - implicit def catsLawsArbitraryForZipVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipVector[A]] = - Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.map(v => new ZipVector(v))) - - implicit def catsLawsArbitraryForZipList[A](implicit A: Arbitrary[A]): Arbitrary[ZipList[A]] = - Arbitrary(implicitly[Arbitrary[List[A]]].arbitrary.map(v => new ZipList(v))) - - implicit def catsLawsArbitraryForZipStream[A](implicit A: Arbitrary[A]): Arbitrary[ZipStream[A]] = - Arbitrary(implicitly[Arbitrary[Stream[A]]].arbitrary.map(v => new ZipStream(v))) - - implicit def catsLawsArbitraryForZipNonEmptyVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipNonEmptyVector[A]] = - Arbitrary(implicitly[Arbitrary[NonEmptyVector[A]]].arbitrary.map(nev => new ZipNonEmptyVector(nev))) - implicit def catsLawsArbitraryForNonEmptyList[A](implicit A: Arbitrary[A]): Arbitrary[NonEmptyList[A]] = Arbitrary(implicitly[Arbitrary[List[A]]].arbitrary.flatMap(fa => A.arbitrary.map(a => NonEmptyList(a, fa)))) implicit def catsLawsCogenForNonEmptyList[A](implicit A: Cogen[A]): Cogen[NonEmptyList[A]] = Cogen[List[A]].contramap(_.toList) - implicit def catsLawsArbitraryForZipNonEmptyList[A](implicit A: Arbitrary[A]): Arbitrary[ZipNonEmptyList[A]] = - Arbitrary(implicitly[Arbitrary[NonEmptyList[A]]].arbitrary.map(nel => new ZipNonEmptyList(nel))) implicit def catsLawsArbitraryForEitherT[F[_], A, B](implicit F: Arbitrary[F[Either[A, B]]]): Arbitrary[EitherT[F, A, B]] = Arbitrary(F.arbitrary.map(EitherT(_))) diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index e37794d0dc..b798e123d7 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -1,8 +1,8 @@ package cats package tests -import cats.data.{NonEmptyList, ZipList} -import cats.laws.discipline.{ApplyTests, TraverseTests, CoflatMapTests, AlternativeTests, SerializableTests, CartesianTests} +import cats.data.{NonEmptyList} +import cats.laws.discipline.{TraverseTests, CoflatMapTests, AlternativeTests, SerializableTests, CartesianTests} import cats.laws.discipline.arbitrary._ class ListTests extends CatsSuite { @@ -19,7 +19,6 @@ class ListTests extends CatsSuite { checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) - checkAll("ZipList[Int]", ApplyTests[ZipList].apply[Int, Int, Int]) test("nel => list => nel returns original nel")( forAll { fa: NonEmptyList[Int] => diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index e4ab424e2b..c0941aec72 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -2,10 +2,9 @@ package cats package tests import cats.kernel.laws.{GroupLaws, OrderLaws} -import cats.data.NonEmptyList.ZipNonEmptyList import cats.data.{NonEmptyList, NonEmptyVector} import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{ApplyTests, ComonadTests, NonEmptyTraverseTests, MonadTests, ReducibleTests, SemigroupKTests, SerializableTests} +import cats.laws.discipline.{ComonadTests, NonEmptyTraverseTests, MonadTests, ReducibleTests, SemigroupKTests, SerializableTests} class NonEmptyListTests extends CatsSuite { // Lots of collections here.. telling ScalaCheck to calm down a bit @@ -35,7 +34,6 @@ class NonEmptyListTests extends CatsSuite { checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].eqv) checkAll("Eq[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(Eq[NonEmptyList[ListWrapper[Int]]])) - checkAll("ZipNonEmptyList[Int]", ApplyTests[ZipNonEmptyList].apply[Int, Int, Int]) { implicit val A = ListWrapper.partialOrder[Int] diff --git a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala index 64f393ac1b..c09feed1dd 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala @@ -4,9 +4,8 @@ package tests import catalysts.Platform import cats.kernel.laws.{GroupLaws, OrderLaws} -import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data.NonEmptyVector -import cats.laws.discipline.{ApplyTests, ComonadTests, SemigroupKTests, FoldableTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests, MonadTests} +import cats.laws.discipline.{ComonadTests, SemigroupKTests, FoldableTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests, MonadTests} import cats.laws.discipline.arbitrary._ import scala.util.Properties @@ -53,8 +52,6 @@ class NonEmptyVectorTests extends CatsSuite { checkAll("NonEmptyVector[Int]", MonadTests[NonEmptyVector].monad[Int, Int, Int]) checkAll("Monad[NonEmptyVector]", SerializableTests.serializable(Monad[NonEmptyVector])) - checkAll("ZipNonEmptyVector[Int]", ApplyTests[ZipNonEmptyVector].apply[Int, Int, Int]) - test("size is consistent with toList.size") { forAll { (nonEmptyVector: NonEmptyVector[Int]) => nonEmptyVector.size should === (nonEmptyVector.toList.size.toLong) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index c569b28659..bd0cd52359 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -5,7 +5,7 @@ import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.instances.stream._ import cats.data.{NonEmptyStream, OneAnd} -import cats.laws.discipline.{ApplicativeTests, ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, NonEmptyTraverseTests, ReducibleTests} +import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, NonEmptyTraverseTests, ReducibleTests} import cats.laws.discipline.arbitrary._ class OneAndTests extends CatsSuite { @@ -37,12 +37,6 @@ class OneAndTests extends CatsSuite { checkAll("MonadTests[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Monad[OneAnd[ListWrapper, ?]])) } - { - implicit val alternative = ListWrapper.alternative - checkAll("OneAnd[ListWrapper, Int]", ApplicativeTests[OneAnd[ListWrapper, ?]].applicative[Int, Int, Int]) - checkAll("Applicative[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Applicative[OneAnd[ListWrapper, ?]])) - } - { implicit val functor = ListWrapper.functor checkAll("OneAnd[ListWrapper, Int]", FunctorTests[OneAnd[ListWrapper, ?]].functor[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 39b3b9bcc1..aa7bf31cee 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -1,12 +1,10 @@ package cats -import cats.data.NonEmptyList.ZipNonEmptyList -import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data._ import cats.tests.CatsSuite import org.scalatest.FunSuite -import cats.laws.discipline.{ApplicativeErrorTests, NonEmptyParallelTests, SerializableTests, ParallelTests => ParallelTypeclassTests} +import cats.laws.discipline.{ApplicativeErrorTests, SerializableTests, ParallelTests => ParallelTypeclassTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -38,13 +36,13 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } test("ParNonEmptyTraverse identity should be equivalent to parNonEmptySequence") { - forAll { es: NonEmptyVector[List[Int]] => + forAll { es: NonEmptyVector[Either[String, Int]] => (Parallel.parNonEmptyTraverse(es)(identity)) should === (Parallel.parNonEmptySequence(es)) } } test("ParNonEmptyTraverse_ identity should be equivalent to parNonEmptySequence_") { - forAll { es: NonEmptyList[List[Int]] => + forAll { es: NonEmptyList[Either[String, Int]] => (Parallel.parNonEmptyTraverse_(es)(identity)) should === (Parallel.parNonEmptySequence_(es)) } } @@ -77,71 +75,15 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } - test("ParMap over NonEmptyList should be consistent with zip") { - forAll { (as: NonEmptyList[Int], bs: NonEmptyList[Int], cs: NonEmptyList[Int]) => - (as, bs, cs).parMapN(_ + _ + _) should === (as.zipWith(bs)(_ + _).zipWith(cs)(_ + _)) - } - } - - test("ParMap over NonEmptyVector should be consistent with zip") { - forAll { (as: NonEmptyVector[Int], bs: NonEmptyVector[Int], cs: NonEmptyVector[Int]) => - (as, bs, cs).parMapN(_ + _ + _) should === (as.zipWith(bs)(_ + _).zipWith(cs)(_ + _)) - } - } - - test("ParMap over List should be consistent with zip") { - forAll { (as: List[Int], bs: List[Int], cs: List[Int]) => - val zipped = as.zip(bs).map { - case (a, b) => a + b - }.zip(cs).map { - case (a, b) => a + b - } - - (as, bs, cs).parMapN(_ + _ + _) should === (zipped) - } - } - - test("ParMap over Vector should be consistent with zip") { - forAll { (as: Vector[Int], bs: Vector[Int], cs: Vector[Int]) => - val zipped = as.zip(bs).map { - case (a, b) => a + b - }.zip(cs).map { - case (a, b) => a + b - } - - (as, bs, cs).parMapN(_ + _ + _) should === (zipped) - } - } - - test("ParMap over Stream should be consistent with zip") { - forAll { (as: Stream[Int], bs: Stream[Int], cs: Stream[Int]) => - val zipped = as.zip(bs).map { - case (a, b) => a + b - }.zip(cs).map { - case (a, b) => a + b - } - - (as, bs, cs).parMapN(_ + _ + _) should === (zipped) - } - } checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?], Int].parallel) checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) - checkAll("NonEmptyParallel[Vector, ZipVector]", NonEmptyParallelTests[Vector, ZipVector, Int].nonEmptyParallel) - checkAll("NonEmptyParallel[List, ZipList]", NonEmptyParallelTests[List, ZipList, Int].nonEmptyParallel) - // Can't test Parallel here, as Applicative[ZipStream].pure doesn't terminate - checkAll("Parallel[Stream, ZipStream]", NonEmptyParallelTests[Stream, ZipStream, Int].nonEmptyParallel) - checkAll("NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector]", NonEmptyParallelTests[NonEmptyVector, ZipNonEmptyVector, Int].nonEmptyParallel) - checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", NonEmptyParallelTests[NonEmptyList, ZipNonEmptyList, Int].nonEmptyParallel) - checkAll("Parallel[NonEmptyStream, OneAnd[ZipStream, ?]", ParallelTypeclassTests[NonEmptyStream, OneAnd[ZipStream, ?], Int].parallel) - checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id, Int].parallel) - checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", SerializableTests.serializable(NonEmptyParallel[NonEmptyList, ZipNonEmptyList])) checkAll("Parallel[Either[String, ?], Validated[String, ?]]", SerializableTests.serializable(Parallel[Either[String, ?], Validated[String, ?]])) { diff --git a/tests/src/test/scala/cats/tests/StreamTests.scala b/tests/src/test/scala/cats/tests/StreamTests.scala index f0414e3c43..733405e5cc 100644 --- a/tests/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/src/test/scala/cats/tests/StreamTests.scala @@ -1,9 +1,7 @@ package cats package tests -import cats.data.ZipStream -import cats.laws.discipline.{AlternativeTests, ApplyTests, CartesianTests, CoflatMapTests, MonadTests, SerializableTests, TraverseTests} -import cats.laws.discipline.arbitrary._ +import cats.laws.discipline.{AlternativeTests, CartesianTests, CoflatMapTests, MonadTests, SerializableTests, TraverseTests} class StreamTests extends CatsSuite { checkAll("Stream[Int]", CartesianTests[Stream].cartesian[Int, Int, Int]) @@ -21,9 +19,6 @@ class StreamTests extends CatsSuite { checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) - // Can't test applicative laws as they don't terminate - checkAll("ZipStream[Int]", ApplyTests[ZipStream].apply[Int, Int, Int]) - test("show") { Stream(1, 2, 3).show should === ("Stream(1, ?)") Stream.empty[Int].show should === ("Stream()") diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala index f9e6d130e1..d1251f9414 100644 --- a/tests/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/src/test/scala/cats/tests/VectorTests.scala @@ -1,8 +1,8 @@ package cats package tests -import cats.data.{NonEmptyVector, ZipVector} -import cats.laws.discipline.{AlternativeTests, ApplyTests, CoflatMapTests, SerializableTests, TraverseTests, CartesianTests} +import cats.data.NonEmptyVector +import cats.laws.discipline.{AlternativeTests, CoflatMapTests, SerializableTests, TraverseTests, CartesianTests} import cats.laws.discipline.arbitrary._ class VectorTests extends CatsSuite { @@ -18,8 +18,6 @@ class VectorTests extends CatsSuite { checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector])) - checkAll("ZipVector[Int]", ApplyTests[ZipVector].apply[Int, Int, Int]) - test("show") { Vector(1, 2, 3).show should === ("Vector(1, 2, 3)") From 15d9a45d8155ee80055143286d2d47f1f7528f06 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 30 Sep 2017 12:45:55 +0200 Subject: [PATCH 50/63] Rename methods in NonEmptyParallel --- core/src/main/scala/cats/Parallel.scala | 36 +++++++++++-------------- project/Boilerplate.scala | 4 +-- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 90cadd5a52..60e8767d72 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -10,12 +10,12 @@ trait NonEmptyParallel[M[_], F[_]] extends Serializable { /** * The Apply instance for F[_] */ - def applicative: Apply[F] + def apply: Apply[F] /** * The FlatMap instance for M[_] */ - def monad: FlatMap[M] + def flatMap: FlatMap[M] /** * Natural Transformation from the parallel Apply F[_] to the sequential FlatMap M[_]. @@ -44,15 +44,9 @@ trait Parallel[M[_], F[_]] extends NonEmptyParallel[M, F] { */ def monad: Monad[M] - /** - * Natural Transformation from the parallel Applicative F[_] to the sequential Monad M[_]. - */ - def sequential: F ~> M + override def apply: Apply[F] = applicative - /** - * Natural Transformation from the sequential Monad M[_] to the parallel Applicative F[_]. - */ - def parallel: M ~> F + override def flatMap: FlatMap[M] = monad /** * Provides an `ApplicativeError[F, E]` instance for any F, that has a `Parallel[M, F]` @@ -130,7 +124,7 @@ object Parallel extends ParallelArityFunctions { */ def parNonEmptySequence[T[_]: NonEmptyTraverse, M[_], F[_], A] (tma: T[M[A]])(implicit P: NonEmptyParallel[M, F]): M[T[A]] = { - val fta: F[T[A]] = NonEmptyTraverse[T].nonEmptyTraverse(tma)(P.parallel.apply)(P.applicative) + val fta: F[T[A]] = NonEmptyTraverse[T].nonEmptyTraverse(tma)(P.parallel.apply)(P.apply) P.sequential(fta) } @@ -140,7 +134,7 @@ object Parallel extends ParallelArityFunctions { */ def parNonEmptyTraverse[T[_]: NonEmptyTraverse, M[_], F[_], A, B] (ta: T[A])(f: A => M[B])(implicit P: NonEmptyParallel[M, F]): M[T[B]] = { - val gtb: F[T[B]] = NonEmptyTraverse[T].nonEmptyTraverse(ta)(f andThen P.parallel.apply)(P.applicative) + val gtb: F[T[B]] = NonEmptyTraverse[T].nonEmptyTraverse(ta)(f andThen P.parallel.apply)(P.apply) P.sequential(gtb) } @@ -150,7 +144,7 @@ object Parallel extends ParallelArityFunctions { */ def parNonEmptySequence_[T[_]: Reducible, M[_], F[_], A] (tma: T[M[A]])(implicit P: NonEmptyParallel[M, F]): M[Unit] = { - val fu: F[Unit] = Reducible[T].nonEmptyTraverse_(tma)(P.parallel.apply)(P.applicative) + val fu: F[Unit] = Reducible[T].nonEmptyTraverse_(tma)(P.parallel.apply)(P.apply) P.sequential(fu) } @@ -160,7 +154,7 @@ object Parallel extends ParallelArityFunctions { */ def parNonEmptyTraverse_[T[_]: Reducible, M[_], F[_], A, B] (ta: T[A])(f: A => M[B])(implicit P: NonEmptyParallel[M, F]): M[Unit] = { - val gtb: F[Unit] = Reducible[T].nonEmptyTraverse_(ta)(f andThen P.parallel.apply)(P.applicative) + val gtb: F[Unit] = Reducible[T].nonEmptyTraverse_(ta)(f andThen P.parallel.apply)(P.apply) P.sequential(gtb) } @@ -171,7 +165,7 @@ object Parallel extends ParallelArityFunctions { def parAp[M[_], F[_], A, B](mf: M[A => B]) (ma: M[A]) (implicit P: NonEmptyParallel[M, F]): M[B] = - P.sequential(P.applicative.ap(P.parallel(mf))(P.parallel(ma))) + P.sequential(P.apply.ap(P.parallel(mf))(P.parallel(ma))) /** * Like `Applicative[F].product`, but uses the applicative instance @@ -179,7 +173,7 @@ object Parallel extends ParallelArityFunctions { */ def parProduct[M[_], F[_], A, B](ma: M[A], mb: M[B]) (implicit P: NonEmptyParallel[M, F]): M[(A, B)] = - P.sequential(P.applicative.product(P.parallel(ma), P.parallel(mb))) + P.sequential(P.apply.product(P.parallel(ma), P.parallel(mb))) /** * Like `Applicative[F].ap2`, but uses the applicative instance @@ -189,7 +183,7 @@ object Parallel extends ParallelArityFunctions { (ma: M[A], mb: M[B]) (implicit P: NonEmptyParallel[M, F]): M[Z] = P.sequential( - P.applicative.ap2(P.parallel(ff))(P.parallel(ma), P.parallel(mb)) + P.apply.ap2(P.parallel(ff))(P.parallel(ma), P.parallel(mb)) ) /** @@ -209,12 +203,12 @@ object Parallel extends ParallelArityFunctions { */ def identity[M[_]: Monad]: Parallel[M, M] = new Parallel[M, M] { - def monad: Monad[M] = implicitly[Monad[M]] + val monad: Monad[M] = implicitly[Monad[M]] - def applicative: Applicative[M] = implicitly[Monad[M]] + val applicative: Applicative[M] = implicitly[Monad[M]] - def sequential: M ~> M = FunctionK.id + val sequential: M ~> M = FunctionK.id - def parallel: M ~> M = FunctionK.id + val parallel: M ~> M = FunctionK.id } } diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index 9122512282..037fa13af3 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -212,7 +212,7 @@ object Boilerplate { |package cats |trait ParallelArityFunctions { - def parMap$arity[M[_], F[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] = - - p.monad.map($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } + - p.flatMap.map($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } |} """ } @@ -264,7 +264,7 @@ object Boilerplate { val n = if (arity == 1) { "" } else { arity.toString } val parMap = - if (arity == 1) s"def parMap[F[_], Z](f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] = p.monad.map($tupleArgs)(f)" + if (arity == 1) s"def parMap[F[_], Z](f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] = p.flatMap.map($tupleArgs)(f)" else s"def parMapN[F[_], Z](f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] = Parallel.parMap$arity($tupleArgs)(f)" From ad8a7c4a1e11e35773d4e5130122b7b21ab9ce3a Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 30 Sep 2017 13:14:27 +0200 Subject: [PATCH 51/63] optimize AppError --- core/src/main/scala/cats/Parallel.scala | 13 +++++++++++++ tests/src/test/scala/cats/tests/ParallelTests.scala | 6 ------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 60e8767d72..480dc77153 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -67,6 +67,19 @@ trait Parallel[M[_], F[_]] extends NonEmptyParallel[M, F] { def pure[A](x: A): F[A] = applicative.pure(x) def ap[A, B](ff: F[(A) => B])(fa: F[A]): F[B] = applicative.ap(ff)(fa) + + override def map[A, B](fa: F[A])(f: (A) => B): F[B] = applicative.map(fa)(f) + + override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = applicative.product(fa, fb) + + override def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = applicative.map2(fa, fb)(f) + + override def map2Eval[A, B, Z](fa: F[A], fb: Eval[F[B]])(f: (A, B) => Z): Eval[F[Z]] = + applicative.map2Eval(fa, fb)(f) + + override def unlessA[A](cond: Boolean)(f: => F[A]): F[Unit] = applicative.unlessA(cond)(f) + + override def whenA[A](cond: Boolean)(f: => F[A]): F[Unit] = applicative.whenA(cond)(f) } } diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index aa7bf31cee..f25c0d223f 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -112,10 +112,4 @@ trait ApplicativeErrorForEitherTest extends FunSuite with Discipline { checkAll("ApplicativeError[Validated[String, Int]]", ApplicativeErrorTests[Validated[String, ?], String].applicativeError[Int, Int, Int]) } - - { - implicit val parVal = Parallel[Either[String, ?], Validated[String, ?]].applicativeError - - checkAll("Parallel[Validated[String, Int]].applicativeError", ApplicativeErrorTests[Validated[String, ?], String].applicativeError[Int, Int, Int]) - } } From 71a123910ab05ef51279287dff1fa56299d7156f Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 1 Oct 2017 20:42:11 +0200 Subject: [PATCH 52/63] Add parFlatTraverse and sequence --- core/src/main/scala/cats/Parallel.scala | 42 +++++++++++++++++++ .../test/scala/cats/tests/ParallelTests.scala | 38 ++++++++++++++--- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 480dc77153..62f2140b67 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -111,6 +111,26 @@ object Parallel extends ParallelArityFunctions { P.sequential(gtb) } + /** + * Like `Traverse[A].flatTraverse`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ + def parFlatTraverse[T[_]: Traverse: FlatMap, M[_], F[_], A, B] + (ta: T[A])(f: A => M[T[B]])(implicit P: Parallel[M, F]): M[T[B]] = { + val gtb: F[T[B]] = Traverse[T].flatTraverse(ta)(f andThen P.parallel.apply)(P.applicative, FlatMap[T]) + P.sequential(gtb) + } + + /** + * Like `Traverse[A].flatSequence`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ + def parFlatSequence[T[_]: Traverse: FlatMap, M[_], F[_], A] + (tma: T[M[T[A]]])(implicit P: Parallel[M, F]): M[T[A]] = { + val fta: F[T[A]] = Traverse[T].flatTraverse(tma)(P.parallel.apply)(P.applicative, FlatMap[T]) + P.sequential(fta) + } + /** * Like `Foldable[A].sequence_`, but uses the applicative instance * corresponding to the Parallel instance instead. @@ -151,6 +171,28 @@ object Parallel extends ParallelArityFunctions { P.sequential(gtb) } + + /** + * Like `NonEmptyTraverse[A].nonEmptyFlatTraverse`, but uses the apply instance + * corresponding to the Parallel instance instead. + */ + def parNonEmptyFlatTraverse[T[_]: NonEmptyTraverse: FlatMap, M[_], F[_], A, B] + (ta: T[A])(f: A => M[T[B]])(implicit P: NonEmptyParallel[M, F]): M[T[B]] = { + val gtb: F[T[B]] = NonEmptyTraverse[T].nonEmptyFlatTraverse(ta)(f andThen P.parallel.apply)(P.apply, FlatMap[T]) + P.sequential(gtb) + } + + + /** + * Like `NonEmptyTraverse[A].nonEmptyFlatSequence`, but uses the apply instance + * corresponding to the Parallel instance instead. + */ + def parNonEmptyFlatSequence[T[_]: NonEmptyTraverse: FlatMap, M[_], F[_], A] + (tma: T[M[T[A]]])(implicit P: NonEmptyParallel[M, F]): M[T[A]] = { + val fta: F[T[A]] = NonEmptyTraverse[T].nonEmptyFlatTraverse(tma)(P.parallel.apply)(P.apply, FlatMap[T]) + P.sequential(fta) + } + /** * Like `Reducible[A].nonEmptySequence_`, but uses the apply instance * corresponding to the Parallel instance instead. diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index f25c0d223f..91a9adfe38 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -19,31 +19,59 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { case Left(e) => e }.foldMap(identity) - (es.parSequence.fold(identity, i => Monoid[String].empty)) should === (lefts) + es.parSequence.fold(identity, i => Monoid[String].empty) should === (lefts) } } test("ParTraverse identity should be equivalent to parSequence") { forAll { es: List[Either[String, Int]] => - (es.parTraverse(identity)) should === (es.parSequence) + es.parTraverse(identity) should === (es.parSequence) } } test("ParTraverse_ identity should be equivalent to parSequence_") { forAll { es: Set[Either[String, Int]] => - (Parallel.parTraverse_(es)(identity)) should === (Parallel.parSequence_(es)) + Parallel.parTraverse_(es)(identity) should === (Parallel.parSequence_(es)) } } test("ParNonEmptyTraverse identity should be equivalent to parNonEmptySequence") { forAll { es: NonEmptyVector[Either[String, Int]] => - (Parallel.parNonEmptyTraverse(es)(identity)) should === (Parallel.parNonEmptySequence(es)) + Parallel.parNonEmptyTraverse(es)(identity) should === (Parallel.parNonEmptySequence(es)) } } test("ParNonEmptyTraverse_ identity should be equivalent to parNonEmptySequence_") { forAll { es: NonEmptyList[Either[String, Int]] => - (Parallel.parNonEmptyTraverse_(es)(identity)) should === (Parallel.parNonEmptySequence_(es)) + Parallel.parNonEmptyTraverse_(es)(identity) should === (Parallel.parNonEmptySequence_(es)) + } + } + + test("ParFlatTraverse should be equivalent to parTraverse map flatten") { + forAll { es: List[Either[String, Int]] => + val f: Int => List[Int] = i => List(i, i + 1) + Parallel.parFlatTraverse(es)(e => e.map(f)) should + === (es.parTraverse(e => e.map(f)).map(_.flatten)) + } + } + + test("ParFlatTraverse identity should be equivalent to parFlatSequence") { + forAll { es: List[Either[String, List[Int]]] => + Parallel.parFlatTraverse(es)(identity) should === (Parallel.parFlatSequence(es)) + } + } + + test("ParNonEmptyFlatTraverse should be equivalent to parNonEmptyTraverse map flatten") { + forAll { es: NonEmptyList[Either[String, Int]] => + val f: Int => NonEmptyList[Int] = i => NonEmptyList.of(i, i + 1) + Parallel.parNonEmptyFlatTraverse(es)(e => e.map(f)) should + === (Parallel.parNonEmptyTraverse(es)(e => e.map(f)).map(_.flatten)) + } + } + + test("ParNonEmptyFlatTraverse identity should be equivalent to parNonEmptyFlatSequence") { + forAll { es: NonEmptyList[Either[String, NonEmptyList[Int]]] => + Parallel.parNonEmptyFlatTraverse(es)(identity) should === (Parallel.parNonEmptyFlatSequence(es)) } } From eb274cfd4b9dc8bba4d1ff0a54f1a822b7dfd84b Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 29 Sep 2017 16:35:03 +0200 Subject: [PATCH 53/63] Revert "Remove FailFastFuture and all Zip* instances" This reverts commit c5e34236ff0ecde627c6104a96b30d632f8ce03e. --- .../main/scala/cats/data/FailFastFuture.scala | 35 +++++++++++ .../main/scala/cats/data/NonEmptyList.scala | 35 +++++++++++ .../main/scala/cats/data/NonEmptyVector.scala | 32 ++++++++++ core/src/main/scala/cats/data/OneAnd.scala | 42 +++++++++++-- core/src/main/scala/cats/data/ZipList.scala | 26 ++++++++ core/src/main/scala/cats/data/ZipStream.scala | 31 ++++++++++ core/src/main/scala/cats/data/ZipVector.scala | 22 +++++++ .../main/scala/cats/instances/parallel.scala | 56 ++++++++++++++++- .../test/scala/cats/tests/FutureTests.scala | 10 +++- .../test/scala/cats/tests/FutureTests.scala | 46 +++++++++++++- .../cats/laws/discipline/Arbitrary.scala | 16 +++++ .../src/test/scala/cats/tests/ListTests.scala | 5 +- .../scala/cats/tests/NonEmptyListTests.scala | 4 +- .../cats/tests/NonEmptyVectorTests.scala | 5 +- .../test/scala/cats/tests/OneAndTests.scala | 8 ++- .../test/scala/cats/tests/ParallelTests.scala | 60 ++++++++++++++++++- .../test/scala/cats/tests/StreamTests.scala | 7 ++- .../test/scala/cats/tests/VectorTests.scala | 6 +- 18 files changed, 430 insertions(+), 16 deletions(-) create mode 100644 core/src/main/scala/cats/data/FailFastFuture.scala create mode 100644 core/src/main/scala/cats/data/ZipList.scala create mode 100644 core/src/main/scala/cats/data/ZipStream.scala create mode 100644 core/src/main/scala/cats/data/ZipVector.scala diff --git a/core/src/main/scala/cats/data/FailFastFuture.scala b/core/src/main/scala/cats/data/FailFastFuture.scala new file mode 100644 index 0000000000..0237174025 --- /dev/null +++ b/core/src/main/scala/cats/data/FailFastFuture.scala @@ -0,0 +1,35 @@ +package cats.data + +import cats.Applicative + +import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.util.{Failure, Success} + +class FailFastFuture[A](val value: Future[A]) extends AnyVal + +object FailFastFuture { + + def apply[A](value: Future[A]): FailFastFuture[A] = new FailFastFuture(value) + + def catsDataApplicativeForFailFastFuture(implicit ec: ExecutionContext): Applicative[FailFastFuture] = + new Applicative[FailFastFuture] { + override def pure[A](x: A): FailFastFuture[A] = FailFastFuture(Future.successful(x)) + + override def ap[A, B](ff: FailFastFuture[(A) => B])(fa: FailFastFuture[A]): FailFastFuture[B] = { + val p = Promise[B]() + + ff.value.onComplete { + case Failure(t) => p.tryFailure(t) + case Success(_) => () + } + + fa.value.onComplete { + case Failure(t) => p.tryFailure(t) + case Success(_) => () + } + + p.tryCompleteWith(ff.value.flatMap(f => fa.value.map(f))) + FailFastFuture(p.future) + } + } +} diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 9e3cd4f089..fa4f9a32e6 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -1,6 +1,7 @@ package cats package data +import cats.data.NonEmptyList.ZipNonEmptyList import cats.instances.list._ import cats.syntax.order._ @@ -390,6 +391,26 @@ object NonEmptyList extends NonEmptyListInstances { def fromReducible[F[_], A](fa: F[A])(implicit F: Reducible[F]): NonEmptyList[A] = F.toNonEmptyList(fa) + class ZipNonEmptyList[A](val value: NonEmptyList[A]) extends AnyVal + + object ZipNonEmptyList { + + def apply[A](nev: NonEmptyList[A]): ZipNonEmptyList[A] = + new ZipNonEmptyList(nev) + + implicit val zipNelApply: Apply[ZipNonEmptyList] = new Apply[ZipNonEmptyList] { + def ap[A, B](ff: ZipNonEmptyList[A => B])(fa: ZipNonEmptyList[A]): ZipNonEmptyList[B] = + ZipNonEmptyList(ff.value.zipWith(fa.value)(_ apply _)) + + override def map[A, B](fa: ZipNonEmptyList[A])(f: (A) => B): ZipNonEmptyList[B] = + ZipNonEmptyList(fa.value.map(f)) + + override def product[A, B](fa: ZipNonEmptyList[A], fb: ZipNonEmptyList[B]): ZipNonEmptyList[(A, B)] = + ZipNonEmptyList(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) + } + + implicit def zipNelEq[A: Eq]: Eq[ZipNonEmptyList[A]] = Eq.by(_.value) + } } private[data] sealed abstract class NonEmptyListInstances extends NonEmptyListInstances0 { @@ -503,6 +524,20 @@ private[data] sealed abstract class NonEmptyListInstances extends NonEmptyListIn new NonEmptyListOrder[A] { val A0 = A } + + implicit def catsDataNonEmptyParallelForNonEmptyList[A]: NonEmptyParallel[NonEmptyList, ZipNonEmptyList] = + new NonEmptyParallel[NonEmptyList, ZipNonEmptyList] { + + def flatMap: FlatMap[NonEmptyList] = NonEmptyList.catsDataInstancesForNonEmptyList + + def apply: Apply[ZipNonEmptyList] = ZipNonEmptyList.zipNelApply + + def sequential: ZipNonEmptyList ~> NonEmptyList = + λ[ZipNonEmptyList ~> NonEmptyList](_.value) + + def parallel: NonEmptyList ~> ZipNonEmptyList = + λ[NonEmptyList ~> ZipNonEmptyList](nel => new ZipNonEmptyList(nel)) + } } private[data] sealed abstract class NonEmptyListInstances0 extends NonEmptyListInstances1 { diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index b37db559ae..20c46c3763 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -1,6 +1,7 @@ package cats package data +import cats.data.NonEmptyVector.ZipNonEmptyVector import scala.annotation.tailrec import scala.collection.immutable.{TreeSet, VectorBuilder} import cats.instances.vector._ @@ -317,6 +318,18 @@ private[data] sealed abstract class NonEmptyVectorInstances { implicit def catsDataSemigroupForNonEmptyVector[A]: Semigroup[NonEmptyVector[A]] = catsDataInstancesForNonEmptyVector.algebra + implicit def catsDataParallelForNonEmptyVector[A]: NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector] = + new NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector] { + + def apply: Apply[ZipNonEmptyVector] = ZipNonEmptyVector.zipNevApply + def flatMap: FlatMap[NonEmptyVector] = NonEmptyVector.catsDataInstancesForNonEmptyVector + + def sequential: ZipNonEmptyVector ~> NonEmptyVector = + λ[ZipNonEmptyVector ~> NonEmptyVector](_.value) + + def parallel: NonEmptyVector ~> ZipNonEmptyVector = + λ[NonEmptyVector ~> ZipNonEmptyVector](nev => new ZipNonEmptyVector(nev)) + } } @@ -343,5 +356,24 @@ object NonEmptyVector extends NonEmptyVectorInstances with Serializable { if (vector.nonEmpty) new NonEmptyVector(vector) else throw new IllegalArgumentException("Cannot create NonEmptyVector from empty vector") + class ZipNonEmptyVector[A](val value: NonEmptyVector[A]) extends Serializable + + object ZipNonEmptyVector { + + def apply[A](nev: NonEmptyVector[A]): ZipNonEmptyVector[A] = + new ZipNonEmptyVector(nev) + implicit val zipNevApply: Apply[ZipNonEmptyVector] = new Apply[ZipNonEmptyVector] { + def ap[A, B](ff: ZipNonEmptyVector[A => B])(fa: ZipNonEmptyVector[A]): ZipNonEmptyVector[B] = + ZipNonEmptyVector(ff.value.zipWith(fa.value)(_ apply _)) + + override def map[A, B](fa: ZipNonEmptyVector[A])(f: (A) => B): ZipNonEmptyVector[B] = + ZipNonEmptyVector(fa.value.map(f)) + + override def product[A, B](fa: ZipNonEmptyVector[A], fb: ZipNonEmptyVector[B]): ZipNonEmptyVector[(A, B)] = + ZipNonEmptyVector(fa.value.zipWith(fb.value){ case (a, b) => (a, b) }) + } + + implicit def zipNevEq[A: Eq]: Eq[ZipNonEmptyVector[A]] = Eq.by(_.value) + } } diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 038b9eb719..7c5d0d2242 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -99,7 +99,22 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { } -private[data] sealed abstract class OneAndInstances extends OneAndLowPriority3 { +private[data] sealed abstract class OneAndInstances extends OneAndLowPriority4 { + + implicit def catsDataParallelForOneAnd[A, M[_]: Alternative, F[_]: Alternative] + (implicit P: Parallel[M, F]): Parallel[OneAnd[M, ?], OneAnd[F, ?]] = + new Parallel[OneAnd[M, ?], OneAnd[F, ?]] { + def monad: Monad[OneAnd[M, ?]] = catsDataMonadForOneAnd(P.monad, Alternative[M]) + + def applicative: Applicative[OneAnd[F, ?]] = catsDataApplicativeForOneAnd(Alternative[F]) + + def sequential: OneAnd[F, ?] ~> OneAnd[M, ?] = + λ[OneAnd[F, ?] ~> OneAnd[M, ?]](ofa => OneAnd(ofa.head, P.sequential(ofa.tail))) + + def parallel: OneAnd[M, ?] ~> OneAnd[F, ?] = + λ[OneAnd[M, ?] ~> OneAnd[F, ?]](ofa => OneAnd(ofa.head, P.parallel(ofa.tail))) + + } implicit def catsDataEqForOneAnd[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] = @@ -201,9 +216,28 @@ private[data] sealed abstract class OneAndLowPriority0 { } } - private[data] sealed abstract class OneAndLowPriority1 extends OneAndLowPriority0 { + implicit def catsDataApplicativeForOneAnd[F[_]](implicit F: Alternative[F]): Applicative[OneAnd[F, ?]] = + new Applicative[OneAnd[F, ?]] { + override def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = + fa.map(f) + + def pure[A](x: A): OneAnd[F, A] = + OneAnd(x, F.empty) + + override def ap[A, B](ff: OneAnd[F, A => B])(fa: OneAnd[F, A]): OneAnd[F, B] = { + val (f, tf) = (ff.head, ff.tail) + val (a, ta) = (fa.head, fa.tail) + val fb = F.ap(tf)(F.combineK(F.pure(a), ta)) + OneAnd(f(a), F.combineK(F.map(ta)(f), fb)) + } + } + +} + +private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority1 { + implicit def catsDataFunctorForOneAnd[F[_]](implicit F: Functor[F]): Functor[OneAnd[F, ?]] = new Functor[OneAnd[F, ?]] { def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = @@ -212,7 +246,7 @@ private[data] sealed abstract class OneAndLowPriority1 extends OneAndLowPriority } -private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority1 { +private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority2 { implicit def catsDataTraverseForOneAnd[F[_]](implicit F: Traverse[F]): Traverse[OneAnd[F, ?]] = new Traverse[OneAnd[F, ?]] { @@ -231,7 +265,7 @@ private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority } -private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority2 { +private[data] sealed abstract class OneAndLowPriority4 extends OneAndLowPriority3 { implicit def catsDataNonEmptyTraverseForOneAnd[F[_]](implicit F: Traverse[F], F2: Alternative[F]): NonEmptyTraverse[OneAnd[F, ?]] = new NonEmptyReducible[OneAnd[F, ?], F] with NonEmptyTraverse[OneAnd[F, ?]] { diff --git a/core/src/main/scala/cats/data/ZipList.scala b/core/src/main/scala/cats/data/ZipList.scala new file mode 100644 index 0000000000..e123c77843 --- /dev/null +++ b/core/src/main/scala/cats/data/ZipList.scala @@ -0,0 +1,26 @@ +package cats.data + +import cats.{Apply, Eq} +import cats.instances.list.catsKernelStdEqForList + +class ZipList[A](val value: List[A]) extends AnyVal + +object ZipList { + + def apply[A](value: List[A]): ZipList[A] = new ZipList(value) + + implicit val catsDataApplyForZipList: Apply[ZipList] = new Apply[ZipList] { + + override def map[A, B](fa: ZipList[A])(f: (A) => B): ZipList[B] = + ZipList(fa.value.map(f)) + + def ap[A, B](ff: ZipList[A => B])(fa: ZipList[A]): ZipList[B] = + ZipList((ff.value, fa.value).zipped.map(_ apply _)) + + override def product[A, B](fa: ZipList[A], fb: ZipList[B]): ZipList[(A, B)] = + ZipList(fa.value.zip(fb.value)) + + } + + implicit def catsDataEqForZipList[A: Eq]: Eq[ZipList[A]] = Eq.by(_.value) +} diff --git a/core/src/main/scala/cats/data/ZipStream.scala b/core/src/main/scala/cats/data/ZipStream.scala new file mode 100644 index 0000000000..a89ab02099 --- /dev/null +++ b/core/src/main/scala/cats/data/ZipStream.scala @@ -0,0 +1,31 @@ +package cats.data + +import cats.{Alternative, Eq} +import cats.instances.stream._ + +class ZipStream[A](val value: Stream[A]) extends AnyVal + +object ZipStream { + + def apply[A](value: Stream[A]): ZipStream[A] = new ZipStream(value) + + implicit val catsDataAlternativeForZipStream: Alternative[ZipStream] = new Alternative[ZipStream] { + def pure[A](x: A): ZipStream[A] = new ZipStream(Stream.continually(x)) + + override def map[A, B](fa: ZipStream[A])(f: (A) => B): ZipStream[B] = + ZipStream(fa.value.map(f)) + + def ap[A, B](ff: ZipStream[A => B])(fa: ZipStream[A]): ZipStream[B] = + ZipStream((ff.value, fa.value).zipped.map(_ apply _)) + + override def product[A, B](fa: ZipStream[A], fb: ZipStream[B]): ZipStream[(A, B)] = + ZipStream(fa.value.zip(fb.value)) + + def empty[A]: ZipStream[A] = ZipStream(Stream.empty[A]) + + def combineK[A](x: ZipStream[A], y: ZipStream[A]): ZipStream[A] = + ZipStream(Alternative[Stream].combineK(x.value, y.value)) + } + + implicit def catsDataEqForZipStream[A: Eq]: Eq[ZipStream[A]] = Eq.by(_.value) +} diff --git a/core/src/main/scala/cats/data/ZipVector.scala b/core/src/main/scala/cats/data/ZipVector.scala new file mode 100644 index 0000000000..cce9641b3e --- /dev/null +++ b/core/src/main/scala/cats/data/ZipVector.scala @@ -0,0 +1,22 @@ +package cats.data + +import cats.{Apply, Eq} +import cats.instances.vector._ + +class ZipVector[A](val value: Vector[A]) extends AnyVal + +object ZipVector { + + def apply[A](value: Vector[A]): ZipVector[A] = new ZipVector(value) + + implicit val catsDataApplyForZipVector: Apply[ZipVector] = new Apply[ZipVector] { + + override def map[A, B](fa: ZipVector[A])(f: (A) => B): ZipVector[B] = + ZipVector(fa.value.map(f)) + def ap[A, B](ff: ZipVector[A => B])(fa: ZipVector[A]): ZipVector[B] = + ZipVector((ff.value, fa.value).zipped.map(_ apply _)) + + } + + implicit def catsDataEqForZipVector[A: Eq]: Eq[ZipVector[A]] = Eq.by(_.value) +} diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index c0baac289f..4ef2fd5285 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -3,7 +3,9 @@ package cats.instances import cats.data._ import cats.kernel.Semigroup import cats.syntax.either._ -import cats.{Applicative, Functor, Monad, Parallel, ~>} +import cats.{Applicative, Apply, FlatMap, Functor, Monad, NonEmptyParallel, Parallel, ~>} + +import scala.concurrent.{ExecutionContext, Future} trait ParallelInstances extends ParallelInstances1 { implicit def catsParallelForEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { @@ -36,6 +38,58 @@ trait ParallelInstances extends ParallelInstances1 { λ[OptionT[M, ?] ~> Nested[F, Option, ?]](optT => Nested(P.parallel(optT.value))) } + implicit def catsStdNonEmptyParallelForZipList[A]: NonEmptyParallel[List, ZipList] = + new NonEmptyParallel[List, ZipList] { + + def flatMap: FlatMap[List] = cats.instances.list.catsStdInstancesForList + def apply: Apply[ZipList] = ZipList.catsDataApplyForZipList + + def sequential: ZipList ~> List = + λ[ZipList ~> List](_.value) + + def parallel: List ~> ZipList = + λ[List ~> ZipList](v => new ZipList(v)) + } + + implicit def catsStdNonEmptyParallelForZipVector[A]: NonEmptyParallel[Vector, ZipVector] = + new NonEmptyParallel[Vector, ZipVector] { + + def flatMap: FlatMap[Vector] = cats.instances.vector.catsStdInstancesForVector + def apply: Apply[ZipVector] = ZipVector.catsDataApplyForZipVector + + def sequential: ZipVector ~> Vector = + λ[ZipVector ~> Vector](_.value) + + def parallel: Vector ~> ZipVector = + λ[Vector ~> ZipVector](v => new ZipVector(v)) + } + + implicit def catsStdParallelForZipStream[A]: Parallel[Stream, ZipStream] = + new Parallel[Stream, ZipStream] { + + def monad: Monad[Stream] = cats.instances.stream.catsStdInstancesForStream + def applicative: Applicative[ZipStream] = ZipStream.catsDataAlternativeForZipStream + + def sequential: ZipStream ~> Stream = + λ[ZipStream ~> Stream](_.value) + + def parallel: Stream ~> ZipStream = + λ[Stream ~> ZipStream](v => new ZipStream(v)) + } + + implicit def catsStdParallelForFailFastFuture[A](implicit ec: ExecutionContext): Parallel[Future, FailFastFuture] = + new Parallel[Future, FailFastFuture] { + + def monad: Monad[Future] = cats.instances.future.catsStdInstancesForFuture + def applicative: Applicative[FailFastFuture] = FailFastFuture.catsDataApplicativeForFailFastFuture + + def sequential: FailFastFuture ~> Future = + λ[FailFastFuture ~> Future](_.value) + + def parallel: Future ~> FailFastFuture = + λ[Future ~> FailFastFuture](f => FailFastFuture(f)) + } + implicit def catsParallelForEitherTNestedParallelValidated[F[_], M[_], E: Semigroup] (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] = diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 61f4c1010d..0150c809ec 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -2,6 +2,7 @@ package cats package js package tests +import cats.data.FailFastFuture import cats.kernel.laws.GroupLaws import cats.laws.discipline._ import cats.js.instances.Await @@ -10,7 +11,6 @@ import cats.tests.{CatsSuite, ListWrapper} import scala.concurrent.Future import scala.concurrent.duration._ - import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Arbitrary.arbitrary import cats.laws.discipline.arbitrary._ @@ -37,6 +37,13 @@ class FutureTests extends CatsSuite { } } + implicit def eqffa[A: Eq]: Eq[FailFastFuture[A]] = + Eq.by(_.value) + + + implicit def failFastArbitrary[A: Arbitrary]: Arbitrary[FailFastFuture[A]] = + Arbitrary(implicitly[Arbitrary[Future[A]]].arbitrary.map(FailFastFuture.apply)) + implicit val throwableEq: Eq[Throwable] = Eq[String].on(_.toString) @@ -55,6 +62,7 @@ class FutureTests extends CatsSuite { checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) + checkAll("Parallel[Future, FailFastFuture]", ParallelTests[Future, FailFastFuture, Int].parallel) { implicit val F = ListWrapper.semigroup[Int] diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index bf011071a8..4a9a5a025b 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -2,18 +2,21 @@ package cats package jvm package tests +import cats.data.FailFastFuture import cats.kernel.laws.GroupLaws import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.tests.{CatsSuite, ListWrapper} -import scala.concurrent.{Await, Future} +import scala.concurrent.{Await, Future, blocking} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.rng.Seed +import scala.util.{Try, Failure} + class FutureTests extends CatsSuite { val timeout = 3.seconds @@ -28,9 +31,15 @@ class FutureTests extends CatsSuite { } } + implicit def eqffa[A: Eq]: Eq[FailFastFuture[A]] = + Eq.by(_.value) + implicit def cogen[A: Cogen]: Cogen[Future[A]] = Cogen[Future[A]] { (seed: Seed, t: Future[A]) => Cogen[A].perturb(seed, Await.result(t, timeout)) } + implicit def failFastArbitrary[A: Arbitrary]: Arbitrary[FailFastFuture[A]] = + Arbitrary(implicitly[Arbitrary[Future[A]]].arbitrary.map(FailFastFuture.apply)) + implicit val throwableEq: Eq[Throwable] = Eq[String].on(_.toString) @@ -38,9 +47,44 @@ class FutureTests extends CatsSuite { implicit val nonFatalArbitrary: Arbitrary[Throwable] = Arbitrary(arbitrary[Exception].map(identity)) + test("FailFastFuture should fail fast on right side") { + val exA = new Exception("A") + val exB = new Exception("B") + val fa: Future[Int] = Future { + blocking(Thread.sleep(200)) + throw exA + } + + val fb: Future[Int] = Future { + blocking(Thread.sleep(1)) + throw exB + } + + val fab: Future[Int] = (fa, fb).parMapN(_ + _) + Try(Await.result(fab, timeout)) should === (Failure(exB)) + } + + test("FailFastFuture should fail fast on left side") { + val exA = new Exception("A") + val exB = new Exception("B") + val fa: Future[Int] = Future { + blocking(Thread.sleep(1)) + throw exA + } + + val fb: Future[Int] = Future { + blocking(Thread.sleep(200)) + throw exB + } + + val fab: Future[Int] = (fa, fb).parMapN(_ + _) + Try(Await.result(fab, timeout)) should === (Failure(exA)) + } + checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) checkAll("Future", CoflatMapTests[Future].coflatMap[Int, Int, Int]) + checkAll("Parallel[Future, FailFastFuture]", ParallelTests[Future, FailFastFuture, Int].parallel) { implicit val F = ListWrapper.semigroup[Int] diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index e1ecc1b02c..cda217fe76 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -2,6 +2,8 @@ package cats package laws package discipline +import cats.data.NonEmptyList.ZipNonEmptyList +import cats.data.NonEmptyVector.ZipNonEmptyVector import scala.util.{Failure, Success, Try} import cats.data._ import org.scalacheck.{Arbitrary, Cogen, Gen} @@ -48,12 +50,26 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsCogenForNonEmptyVector[A](implicit A: Cogen[A]): Cogen[NonEmptyVector[A]] = Cogen[Vector[A]].contramap(_.toVector) + implicit def catsLawsArbitraryForZipVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipVector[A]] = + Arbitrary(implicitly[Arbitrary[Vector[A]]].arbitrary.map(v => new ZipVector(v))) + + implicit def catsLawsArbitraryForZipList[A](implicit A: Arbitrary[A]): Arbitrary[ZipList[A]] = + Arbitrary(implicitly[Arbitrary[List[A]]].arbitrary.map(v => new ZipList(v))) + + implicit def catsLawsArbitraryForZipStream[A](implicit A: Arbitrary[A]): Arbitrary[ZipStream[A]] = + Arbitrary(implicitly[Arbitrary[Stream[A]]].arbitrary.map(v => new ZipStream(v))) + + implicit def catsLawsArbitraryForZipNonEmptyVector[A](implicit A: Arbitrary[A]): Arbitrary[ZipNonEmptyVector[A]] = + Arbitrary(implicitly[Arbitrary[NonEmptyVector[A]]].arbitrary.map(nev => new ZipNonEmptyVector(nev))) + implicit def catsLawsArbitraryForNonEmptyList[A](implicit A: Arbitrary[A]): Arbitrary[NonEmptyList[A]] = Arbitrary(implicitly[Arbitrary[List[A]]].arbitrary.flatMap(fa => A.arbitrary.map(a => NonEmptyList(a, fa)))) implicit def catsLawsCogenForNonEmptyList[A](implicit A: Cogen[A]): Cogen[NonEmptyList[A]] = Cogen[List[A]].contramap(_.toList) + implicit def catsLawsArbitraryForZipNonEmptyList[A](implicit A: Arbitrary[A]): Arbitrary[ZipNonEmptyList[A]] = + Arbitrary(implicitly[Arbitrary[NonEmptyList[A]]].arbitrary.map(nel => new ZipNonEmptyList(nel))) implicit def catsLawsArbitraryForEitherT[F[_], A, B](implicit F: Arbitrary[F[Either[A, B]]]): Arbitrary[EitherT[F, A, B]] = Arbitrary(F.arbitrary.map(EitherT(_))) diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index b798e123d7..e37794d0dc 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -1,8 +1,8 @@ package cats package tests -import cats.data.{NonEmptyList} -import cats.laws.discipline.{TraverseTests, CoflatMapTests, AlternativeTests, SerializableTests, CartesianTests} +import cats.data.{NonEmptyList, ZipList} +import cats.laws.discipline.{ApplyTests, TraverseTests, CoflatMapTests, AlternativeTests, SerializableTests, CartesianTests} import cats.laws.discipline.arbitrary._ class ListTests extends CatsSuite { @@ -19,6 +19,7 @@ class ListTests extends CatsSuite { checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) + checkAll("ZipList[Int]", ApplyTests[ZipList].apply[Int, Int, Int]) test("nel => list => nel returns original nel")( forAll { fa: NonEmptyList[Int] => diff --git a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala index c0941aec72..e4ab424e2b 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListTests.scala @@ -2,9 +2,10 @@ package cats package tests import cats.kernel.laws.{GroupLaws, OrderLaws} +import cats.data.NonEmptyList.ZipNonEmptyList import cats.data.{NonEmptyList, NonEmptyVector} import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{ComonadTests, NonEmptyTraverseTests, MonadTests, ReducibleTests, SemigroupKTests, SerializableTests} +import cats.laws.discipline.{ApplyTests, ComonadTests, NonEmptyTraverseTests, MonadTests, ReducibleTests, SemigroupKTests, SerializableTests} class NonEmptyListTests extends CatsSuite { // Lots of collections here.. telling ScalaCheck to calm down a bit @@ -34,6 +35,7 @@ class NonEmptyListTests extends CatsSuite { checkAll("NonEmptyList[ListWrapper[Int]]", OrderLaws[NonEmptyList[ListWrapper[Int]]].eqv) checkAll("Eq[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(Eq[NonEmptyList[ListWrapper[Int]]])) + checkAll("ZipNonEmptyList[Int]", ApplyTests[ZipNonEmptyList].apply[Int, Int, Int]) { implicit val A = ListWrapper.partialOrder[Int] diff --git a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala index c09feed1dd..64f393ac1b 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorTests.scala @@ -4,8 +4,9 @@ package tests import catalysts.Platform import cats.kernel.laws.{GroupLaws, OrderLaws} +import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data.NonEmptyVector -import cats.laws.discipline.{ComonadTests, SemigroupKTests, FoldableTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests, MonadTests} +import cats.laws.discipline.{ApplyTests, ComonadTests, SemigroupKTests, FoldableTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests, MonadTests} import cats.laws.discipline.arbitrary._ import scala.util.Properties @@ -52,6 +53,8 @@ class NonEmptyVectorTests extends CatsSuite { checkAll("NonEmptyVector[Int]", MonadTests[NonEmptyVector].monad[Int, Int, Int]) checkAll("Monad[NonEmptyVector]", SerializableTests.serializable(Monad[NonEmptyVector])) + checkAll("ZipNonEmptyVector[Int]", ApplyTests[ZipNonEmptyVector].apply[Int, Int, Int]) + test("size is consistent with toList.size") { forAll { (nonEmptyVector: NonEmptyVector[Int]) => nonEmptyVector.size should === (nonEmptyVector.toList.size.toLong) diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index bd0cd52359..c569b28659 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -5,7 +5,7 @@ import cats.kernel.laws.{GroupLaws, OrderLaws} import cats.instances.stream._ import cats.data.{NonEmptyStream, OneAnd} -import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, NonEmptyTraverseTests, ReducibleTests} +import cats.laws.discipline.{ApplicativeTests, ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, NonEmptyTraverseTests, ReducibleTests} import cats.laws.discipline.arbitrary._ class OneAndTests extends CatsSuite { @@ -37,6 +37,12 @@ class OneAndTests extends CatsSuite { checkAll("MonadTests[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Monad[OneAnd[ListWrapper, ?]])) } + { + implicit val alternative = ListWrapper.alternative + checkAll("OneAnd[ListWrapper, Int]", ApplicativeTests[OneAnd[ListWrapper, ?]].applicative[Int, Int, Int]) + checkAll("Applicative[OneAnd[ListWrapper, A]]", SerializableTests.serializable(Applicative[OneAnd[ListWrapper, ?]])) + } + { implicit val functor = ListWrapper.functor checkAll("OneAnd[ListWrapper, Int]", FunctorTests[OneAnd[ListWrapper, ?]].functor[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 91a9adfe38..e3a38ae243 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -1,10 +1,12 @@ package cats +import cats.data.NonEmptyList.ZipNonEmptyList +import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data._ import cats.tests.CatsSuite import org.scalatest.FunSuite -import cats.laws.discipline.{ApplicativeErrorTests, SerializableTests, ParallelTests => ParallelTypeclassTests} +import cats.laws.discipline.{ApplicativeErrorTests, NonEmptyParallelTests, SerializableTests, ParallelTests => ParallelTypeclassTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary @@ -103,15 +105,71 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } + test("ParMap over NonEmptyList should be consistent with zip") { + forAll { (as: NonEmptyList[Int], bs: NonEmptyList[Int], cs: NonEmptyList[Int]) => + (as, bs, cs).parMapN(_ + _ + _) should === (as.zipWith(bs)(_ + _).zipWith(cs)(_ + _)) + } + } + + test("ParMap over NonEmptyVector should be consistent with zip") { + forAll { (as: NonEmptyVector[Int], bs: NonEmptyVector[Int], cs: NonEmptyVector[Int]) => + (as, bs, cs).parMapN(_ + _ + _) should === (as.zipWith(bs)(_ + _).zipWith(cs)(_ + _)) + } + } + + test("ParMap over List should be consistent with zip") { + forAll { (as: List[Int], bs: List[Int], cs: List[Int]) => + val zipped = as.zip(bs).map { + case (a, b) => a + b + }.zip(cs).map { + case (a, b) => a + b + } + + (as, bs, cs).parMapN(_ + _ + _) should === (zipped) + } + } + + test("ParMap over Vector should be consistent with zip") { + forAll { (as: Vector[Int], bs: Vector[Int], cs: Vector[Int]) => + val zipped = as.zip(bs).map { + case (a, b) => a + b + }.zip(cs).map { + case (a, b) => a + b + } + + (as, bs, cs).parMapN(_ + _ + _) should === (zipped) + } + } + + test("ParMap over Stream should be consistent with zip") { + forAll { (as: Stream[Int], bs: Stream[Int], cs: Stream[Int]) => + val zipped = as.zip(bs).map { + case (a, b) => a + b + }.zip(cs).map { + case (a, b) => a + b + } + + (as, bs, cs).parMapN(_ + _ + _) should === (zipped) + } + } checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?], Int].parallel) checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) + checkAll("NonEmptyParallel[Vector, ZipVector]", NonEmptyParallelTests[Vector, ZipVector, Int].nonEmptyParallel) + checkAll("NonEmptyParallel[List, ZipList]", NonEmptyParallelTests[List, ZipList, Int].nonEmptyParallel) + // Can't test Parallel here, as Applicative[ZipStream].pure doesn't terminate + checkAll("Parallel[Stream, ZipStream]", NonEmptyParallelTests[Stream, ZipStream, Int].nonEmptyParallel) + checkAll("NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector]", NonEmptyParallelTests[NonEmptyVector, ZipNonEmptyVector, Int].nonEmptyParallel) + checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", NonEmptyParallelTests[NonEmptyList, ZipNonEmptyList, Int].nonEmptyParallel) + checkAll("Parallel[NonEmptyStream, OneAnd[ZipStream, ?]", ParallelTypeclassTests[NonEmptyStream, OneAnd[ZipStream, ?], Int].parallel) + checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id, Int].parallel) + checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", SerializableTests.serializable(NonEmptyParallel[NonEmptyList, ZipNonEmptyList])) checkAll("Parallel[Either[String, ?], Validated[String, ?]]", SerializableTests.serializable(Parallel[Either[String, ?], Validated[String, ?]])) { diff --git a/tests/src/test/scala/cats/tests/StreamTests.scala b/tests/src/test/scala/cats/tests/StreamTests.scala index 733405e5cc..f0414e3c43 100644 --- a/tests/src/test/scala/cats/tests/StreamTests.scala +++ b/tests/src/test/scala/cats/tests/StreamTests.scala @@ -1,7 +1,9 @@ package cats package tests -import cats.laws.discipline.{AlternativeTests, CartesianTests, CoflatMapTests, MonadTests, SerializableTests, TraverseTests} +import cats.data.ZipStream +import cats.laws.discipline.{AlternativeTests, ApplyTests, CartesianTests, CoflatMapTests, MonadTests, SerializableTests, TraverseTests} +import cats.laws.discipline.arbitrary._ class StreamTests extends CatsSuite { checkAll("Stream[Int]", CartesianTests[Stream].cartesian[Int, Int, Int]) @@ -19,6 +21,9 @@ class StreamTests extends CatsSuite { checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) + // Can't test applicative laws as they don't terminate + checkAll("ZipStream[Int]", ApplyTests[ZipStream].apply[Int, Int, Int]) + test("show") { Stream(1, 2, 3).show should === ("Stream(1, ?)") Stream.empty[Int].show should === ("Stream()") diff --git a/tests/src/test/scala/cats/tests/VectorTests.scala b/tests/src/test/scala/cats/tests/VectorTests.scala index d1251f9414..f9e6d130e1 100644 --- a/tests/src/test/scala/cats/tests/VectorTests.scala +++ b/tests/src/test/scala/cats/tests/VectorTests.scala @@ -1,8 +1,8 @@ package cats package tests -import cats.data.NonEmptyVector -import cats.laws.discipline.{AlternativeTests, CoflatMapTests, SerializableTests, TraverseTests, CartesianTests} +import cats.data.{NonEmptyVector, ZipVector} +import cats.laws.discipline.{AlternativeTests, ApplyTests, CoflatMapTests, SerializableTests, TraverseTests, CartesianTests} import cats.laws.discipline.arbitrary._ class VectorTests extends CatsSuite { @@ -18,6 +18,8 @@ class VectorTests extends CatsSuite { checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, List[Int], Option, Option]) checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector])) + checkAll("ZipVector[Int]", ApplyTests[ZipVector].apply[Int, Int, Int]) + test("show") { Vector(1, 2, 3).show should === ("Vector(1, 2, 3)") From 4151e7ebfed0e30966425ae03910d6f7751e056f Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 1 Oct 2017 20:42:11 +0200 Subject: [PATCH 54/63] Add parFlatTraverse and sequence --- core/src/main/scala/cats/Parallel.scala | 42 +++++++++++++++++++ .../src/test/scala/cats/tests/ListTests.scala | 2 +- .../test/scala/cats/tests/ParallelTests.scala | 38 ++++++++++++++--- .../test/scala/cats/tests/SyntaxTests.scala | 2 +- 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/Parallel.scala b/core/src/main/scala/cats/Parallel.scala index 480dc77153..62f2140b67 100644 --- a/core/src/main/scala/cats/Parallel.scala +++ b/core/src/main/scala/cats/Parallel.scala @@ -111,6 +111,26 @@ object Parallel extends ParallelArityFunctions { P.sequential(gtb) } + /** + * Like `Traverse[A].flatTraverse`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ + def parFlatTraverse[T[_]: Traverse: FlatMap, M[_], F[_], A, B] + (ta: T[A])(f: A => M[T[B]])(implicit P: Parallel[M, F]): M[T[B]] = { + val gtb: F[T[B]] = Traverse[T].flatTraverse(ta)(f andThen P.parallel.apply)(P.applicative, FlatMap[T]) + P.sequential(gtb) + } + + /** + * Like `Traverse[A].flatSequence`, but uses the applicative instance + * corresponding to the Parallel instance instead. + */ + def parFlatSequence[T[_]: Traverse: FlatMap, M[_], F[_], A] + (tma: T[M[T[A]]])(implicit P: Parallel[M, F]): M[T[A]] = { + val fta: F[T[A]] = Traverse[T].flatTraverse(tma)(P.parallel.apply)(P.applicative, FlatMap[T]) + P.sequential(fta) + } + /** * Like `Foldable[A].sequence_`, but uses the applicative instance * corresponding to the Parallel instance instead. @@ -151,6 +171,28 @@ object Parallel extends ParallelArityFunctions { P.sequential(gtb) } + + /** + * Like `NonEmptyTraverse[A].nonEmptyFlatTraverse`, but uses the apply instance + * corresponding to the Parallel instance instead. + */ + def parNonEmptyFlatTraverse[T[_]: NonEmptyTraverse: FlatMap, M[_], F[_], A, B] + (ta: T[A])(f: A => M[T[B]])(implicit P: NonEmptyParallel[M, F]): M[T[B]] = { + val gtb: F[T[B]] = NonEmptyTraverse[T].nonEmptyFlatTraverse(ta)(f andThen P.parallel.apply)(P.apply, FlatMap[T]) + P.sequential(gtb) + } + + + /** + * Like `NonEmptyTraverse[A].nonEmptyFlatSequence`, but uses the apply instance + * corresponding to the Parallel instance instead. + */ + def parNonEmptyFlatSequence[T[_]: NonEmptyTraverse: FlatMap, M[_], F[_], A] + (tma: T[M[T[A]]])(implicit P: NonEmptyParallel[M, F]): M[T[A]] = { + val fta: F[T[A]] = NonEmptyTraverse[T].nonEmptyFlatTraverse(tma)(P.parallel.apply)(P.apply, FlatMap[T]) + P.sequential(fta) + } + /** * Like `Reducible[A].nonEmptySequence_`, but uses the apply instance * corresponding to the Parallel instance instead. diff --git a/tests/src/test/scala/cats/tests/ListTests.scala b/tests/src/test/scala/cats/tests/ListTests.scala index b798e123d7..922fe9d1f1 100644 --- a/tests/src/test/scala/cats/tests/ListTests.scala +++ b/tests/src/test/scala/cats/tests/ListTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{NonEmptyList} +import cats.data.NonEmptyList import cats.laws.discipline.{TraverseTests, CoflatMapTests, AlternativeTests, SerializableTests, CartesianTests} import cats.laws.discipline.arbitrary._ diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index f25c0d223f..91a9adfe38 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -19,31 +19,59 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { case Left(e) => e }.foldMap(identity) - (es.parSequence.fold(identity, i => Monoid[String].empty)) should === (lefts) + es.parSequence.fold(identity, i => Monoid[String].empty) should === (lefts) } } test("ParTraverse identity should be equivalent to parSequence") { forAll { es: List[Either[String, Int]] => - (es.parTraverse(identity)) should === (es.parSequence) + es.parTraverse(identity) should === (es.parSequence) } } test("ParTraverse_ identity should be equivalent to parSequence_") { forAll { es: Set[Either[String, Int]] => - (Parallel.parTraverse_(es)(identity)) should === (Parallel.parSequence_(es)) + Parallel.parTraverse_(es)(identity) should === (Parallel.parSequence_(es)) } } test("ParNonEmptyTraverse identity should be equivalent to parNonEmptySequence") { forAll { es: NonEmptyVector[Either[String, Int]] => - (Parallel.parNonEmptyTraverse(es)(identity)) should === (Parallel.parNonEmptySequence(es)) + Parallel.parNonEmptyTraverse(es)(identity) should === (Parallel.parNonEmptySequence(es)) } } test("ParNonEmptyTraverse_ identity should be equivalent to parNonEmptySequence_") { forAll { es: NonEmptyList[Either[String, Int]] => - (Parallel.parNonEmptyTraverse_(es)(identity)) should === (Parallel.parNonEmptySequence_(es)) + Parallel.parNonEmptyTraverse_(es)(identity) should === (Parallel.parNonEmptySequence_(es)) + } + } + + test("ParFlatTraverse should be equivalent to parTraverse map flatten") { + forAll { es: List[Either[String, Int]] => + val f: Int => List[Int] = i => List(i, i + 1) + Parallel.parFlatTraverse(es)(e => e.map(f)) should + === (es.parTraverse(e => e.map(f)).map(_.flatten)) + } + } + + test("ParFlatTraverse identity should be equivalent to parFlatSequence") { + forAll { es: List[Either[String, List[Int]]] => + Parallel.parFlatTraverse(es)(identity) should === (Parallel.parFlatSequence(es)) + } + } + + test("ParNonEmptyFlatTraverse should be equivalent to parNonEmptyTraverse map flatten") { + forAll { es: NonEmptyList[Either[String, Int]] => + val f: Int => NonEmptyList[Int] = i => NonEmptyList.of(i, i + 1) + Parallel.parNonEmptyFlatTraverse(es)(e => e.map(f)) should + === (Parallel.parNonEmptyTraverse(es)(e => e.map(f)).map(_.flatten)) + } + } + + test("ParNonEmptyFlatTraverse identity should be equivalent to parNonEmptyFlatSequence") { + forAll { es: NonEmptyList[Either[String, NonEmptyList[Int]]] => + Parallel.parNonEmptyFlatTraverse(es)(identity) should === (Parallel.parNonEmptyFlatSequence(es)) } } diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index 2511e2ab6e..d25ba49c51 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -163,7 +163,7 @@ object SyntaxTests extends AllInstances with AllSyntax { val mta = tma.parSequence } - def testParallelTuple[M[_]: Monad, F[_], A, B, C, Z](implicit P: Parallel[M, F]) = { + def testParallelTuple[M[_]: Monad, F[_], A, B, C, Z](implicit P: NonEmptyParallel[M, F]) = { val tfabc = mock[(M[A], M[B], M[C])] val fa = mock[M[A]] val fb = mock[M[B]] From 4a9f2ad68a683c31acfd13138b734850cf94dee7 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 14 Oct 2017 10:50:04 -0400 Subject: [PATCH 55/63] Add isomorphic functor law --- laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala | 3 +++ .../scala/cats/laws/discipline/NonEmptyParallelTests.scala | 5 +++-- laws/src/main/scala/cats/laws/discipline/ParallelTests.scala | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala b/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala index 94ddb59198..423a9e0ae0 100644 --- a/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala +++ b/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala @@ -13,6 +13,9 @@ trait NonEmptyParallelLaws[M[_], F[_]] { def sequentialRoundTrip[A](fa: F[A]): IsEq[F[A]] = P.parallel(P.sequential(fa)) <-> fa + + def isomorphicFunctor[A, B](fa: F[A], f: A => A): IsEq[M[A]] = + P.flatMap.map(P.sequential(fa))(f) <-> P.sequential(P.apply.map(fa)(f)) } object NonEmptyParallelLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala index 369b0a0c4b..453d85857a 100644 --- a/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala @@ -10,12 +10,13 @@ trait NonEmptyParallelTests[M[_], F[_], A] extends Laws { def laws: NonEmptyParallelLaws[M, F] def nonEmptyParallel - (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = + (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], Arbf: Arbitrary[A => A], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = new DefaultRuleSet( "parallel", None, "parallel round trip" -> forAll((ma: M[A]) => laws.parallelRoundTrip(ma)), - "sequential round trip" -> forAll((fa: F[A]) => laws.sequentialRoundTrip(fa)) + "sequential round trip" -> forAll((fa: F[A]) => laws.sequentialRoundTrip(fa)), + "isomorphic functor" -> forAll((fa: F[A], f: A => A) => laws.isomorphicFunctor(fa, f)) ) } diff --git a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala index b5df4102be..cbf8c49ed3 100644 --- a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala @@ -9,7 +9,7 @@ trait ParallelTests[M[_], F[_], A] extends NonEmptyParallelTests[M, F, A] { def laws: ParallelLaws[M, F] def parallel - (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = + (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], Arbf: Arbitrary[A => A], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = new DefaultRuleSet( "parallel", Some(nonEmptyParallel), From 17acb6f843c9c80ae79bfe3046602dc65fa4015d Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 14 Oct 2017 15:15:42 -0400 Subject: [PATCH 56/63] Add ZipMap? --- core/src/main/scala/cats/data/ZipMap.scala | 22 +++++++++++++++++++ .../main/scala/cats/instances/parallel.scala | 13 +++++++++++ 2 files changed, 35 insertions(+) create mode 100644 core/src/main/scala/cats/data/ZipMap.scala diff --git a/core/src/main/scala/cats/data/ZipMap.scala b/core/src/main/scala/cats/data/ZipMap.scala new file mode 100644 index 0000000000..016084ce2e --- /dev/null +++ b/core/src/main/scala/cats/data/ZipMap.scala @@ -0,0 +1,22 @@ +package cats.data + +import cats.{Apply, Eq, Semigroup} +import cats.instances.map._ + +class ZipMap[K, A](val value: Map[K, A]) extends AnyVal + +object ZipMap { + + def apply[K, A](value: Map[K, A]): ZipMap[K, A] = new ZipMap(value) + + implicit def catsDataApplyForZipMap[K: Semigroup]: Apply[ZipMap[K, ?]] = new Apply[ZipMap[K, ?]] { + + override def map[A, B](fa: ZipMap[K, A])(f: (A) => B): ZipMap[K, B] = + ZipMap(fa.value.map { case (k, a) => (k, f(a)) }) + def ap[A, B](ff: ZipMap[K, A => B])(fa: ZipMap[K, A]): ZipMap[K, B] = + ZipMap((ff.value, fa.value).zipped.map { case ((k1, f), (k2, a)) => (Semigroup[K].combine(k1, k2), f(a)) }) + + } + + implicit def catsDataEqForZipMap[K: Eq, A: Eq]: Eq[ZipMap[K, A]] = Eq.by(_.value) +} \ No newline at end of file diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 4ef2fd5285..bf3dc562b3 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -64,6 +64,19 @@ trait ParallelInstances extends ParallelInstances1 { λ[Vector ~> ZipVector](v => new ZipVector(v)) } + implicit def catsStdNonEmptyParallelForZipMap[K: Semigroup, A]: NonEmptyParallel[Map[K, ?], ZipMap[K, ?]] = + new NonEmptyParallel[Map[K, ?], ZipMap[K, ?]] { + + def flatMap: FlatMap[Map[K, ?]] = cats.instances.map.catsStdInstancesForMap + def apply: Apply[ZipMap[K, ?]] = ZipMap.catsDataApplyForZipMap + + def sequential: ZipMap[K, ?] ~> Map[K, ?] = + λ[ZipMap[K, ?] ~> Map[K, ?]](_.value) + + def parallel: Map[K, ?] ~> ZipMap[K, ?] = + λ[Map[K, ?] ~> ZipMap[K, ?]](v => new ZipMap(v)) + } + implicit def catsStdParallelForZipStream[A]: Parallel[Stream, ZipStream] = new Parallel[Stream, ZipStream] { From 7a6cd524611c5345143bc5d35f17a15ff00fa1e5 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 14 Oct 2017 15:48:05 -0400 Subject: [PATCH 57/63] Fix law test parameters --- .../scala/cats/laws/NonEmptyParallelLaws.scala | 2 +- .../laws/discipline/NonEmptyParallelTests.scala | 12 ++++++------ .../scala/cats/laws/discipline/ParallelTests.scala | 12 ++++++------ .../src/test/scala/cats/tests/ParallelTests.scala | 14 +++++++------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala b/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala index 423a9e0ae0..f3251d0b56 100644 --- a/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala +++ b/laws/src/main/scala/cats/laws/NonEmptyParallelLaws.scala @@ -14,7 +14,7 @@ trait NonEmptyParallelLaws[M[_], F[_]] { def sequentialRoundTrip[A](fa: F[A]): IsEq[F[A]] = P.parallel(P.sequential(fa)) <-> fa - def isomorphicFunctor[A, B](fa: F[A], f: A => A): IsEq[M[A]] = + def isomorphicFunctor[A, B](fa: F[A], f: A => B): IsEq[M[B]] = P.flatMap.map(P.sequential(fa))(f) <-> P.sequential(P.apply.map(fa)(f)) } diff --git a/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala index 453d85857a..0112b8d894 100644 --- a/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/NonEmptyParallelTests.scala @@ -6,21 +6,21 @@ import org.scalacheck.Arbitrary import org.scalacheck.Prop.forAll import org.typelevel.discipline.Laws -trait NonEmptyParallelTests[M[_], F[_], A] extends Laws { +trait NonEmptyParallelTests[M[_], F[_]] extends Laws { def laws: NonEmptyParallelLaws[M, F] - def nonEmptyParallel - (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], Arbf: Arbitrary[A => A], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = + def nonEmptyParallel[A, B] + (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], ArbMb: Arbitrary[M[B]], Arbf: Arbitrary[A => B], EqMa: Eq[M[A]], EqMb: Eq[M[B]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = new DefaultRuleSet( "parallel", None, "parallel round trip" -> forAll((ma: M[A]) => laws.parallelRoundTrip(ma)), "sequential round trip" -> forAll((fa: F[A]) => laws.sequentialRoundTrip(fa)), - "isomorphic functor" -> forAll((fa: F[A], f: A => A) => laws.isomorphicFunctor(fa, f)) + "isomorphic functor" -> forAll((fa: F[A], f: A => B) => laws.isomorphicFunctor(fa, f)) ) } object NonEmptyParallelTests { - def apply[M[_], F[_], A](implicit ev: NonEmptyParallel[M, F]): NonEmptyParallelTests[M, F, A] = - new NonEmptyParallelTests[M, F, A] { val laws: NonEmptyParallelLaws[M, F] = NonEmptyParallelLaws[M, F] } + def apply[M[_], F[_]](implicit ev: NonEmptyParallel[M, F]): NonEmptyParallelTests[M, F] = + new NonEmptyParallelTests[M, F] { val laws: NonEmptyParallelLaws[M, F] = NonEmptyParallelLaws[M, F] } } diff --git a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala index cbf8c49ed3..7c66bdfeb8 100644 --- a/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ParallelTests.scala @@ -5,19 +5,19 @@ package discipline import org.scalacheck.Arbitrary import org.scalacheck.Prop.forAll -trait ParallelTests[M[_], F[_], A] extends NonEmptyParallelTests[M, F, A] { +trait ParallelTests[M[_], F[_]] extends NonEmptyParallelTests[M, F] { def laws: ParallelLaws[M, F] - def parallel - (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], Arbf: Arbitrary[A => A], EqMa: Eq[M[A]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = + def parallel[A, B] + (implicit ArbA: Arbitrary[A], ArbM: Arbitrary[M[A]], ArbMb: Arbitrary[M[B]], Arbf: Arbitrary[A => B], EqMa: Eq[M[A]], EqMb: Eq[M[B]], ArbF: Arbitrary[F[A]], EqFa: Eq[F[A]]): RuleSet = new DefaultRuleSet( "parallel", - Some(nonEmptyParallel), + Some(nonEmptyParallel[A, B]), "isomorphic pure" -> forAll((a: A) => laws.isomorphicPure(a)) ) } object ParallelTests { - def apply[M[_], F[_], A](implicit ev: Parallel[M, F]): ParallelTests[M, F, A] = - new ParallelTests[M, F, A] { val laws: ParallelLaws[M, F] = ParallelLaws[M, F] } + def apply[M[_], F[_]](implicit ev: Parallel[M, F]): ParallelTests[M, F] = + new ParallelTests[M, F] { val laws: ParallelLaws[M, F] = ParallelLaws[M, F] } } diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelTests.scala index 91a9adfe38..a0fe516c56 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelTests.scala @@ -104,13 +104,13 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } - checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?], Int].parallel) - checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?], Int].parallel) - checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?], Int].parallel) - checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?], Int].parallel) - checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?], Int].parallel) + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?]].parallel[Int, String]) + checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?]].parallel[Int, String]) + checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?]].parallel[Int, String]) + checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]].parallel[Int, String]) + checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?]].parallel[Int, String]) - checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id, Int].parallel) + checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id].parallel[Int, String]) checkAll("Parallel[Either[String, ?], Validated[String, ?]]", SerializableTests.serializable(Parallel[Either[String, ?], Validated[String, ?]])) @@ -118,7 +118,7 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) - checkAll("Parallel[KlesliT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?], Int].parallel) + checkAll("Parallel[KlesliT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?]].parallel[Int, String]) } From 782d5681d97e341ca5fd318ba1cf389d6173dcc3 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 18 Oct 2017 20:28:51 -0400 Subject: [PATCH 58/63] Remove ZipMap --- core/src/main/scala/cats/data/ZipMap.scala | 22 ------------------- .../main/scala/cats/instances/parallel.scala | 13 ----------- 2 files changed, 35 deletions(-) delete mode 100644 core/src/main/scala/cats/data/ZipMap.scala diff --git a/core/src/main/scala/cats/data/ZipMap.scala b/core/src/main/scala/cats/data/ZipMap.scala deleted file mode 100644 index d7ee5d7065..0000000000 --- a/core/src/main/scala/cats/data/ZipMap.scala +++ /dev/null @@ -1,22 +0,0 @@ -package cats.data - -import cats.{Apply, Eq, Semigroup} -import cats.instances.map._ - -class ZipMap[K, A](val value: Map[K, A]) extends AnyVal - -object ZipMap { - - def apply[K, A](value: Map[K, A]): ZipMap[K, A] = new ZipMap(value) - - implicit def catsDataApplyForZipMap[K: Semigroup]: Apply[ZipMap[K, ?]] = new Apply[ZipMap[K, ?]] { - - override def map[A, B](fa: ZipMap[K, A])(f: (A) => B): ZipMap[K, B] = - ZipMap(fa.value.map { case (k, a) => (k, f(a)) }) - def ap[A, B](ff: ZipMap[K, A => B])(fa: ZipMap[K, A]): ZipMap[K, B] = - ZipMap((ff.value, fa.value).zipped.map { case ((k1, f), (k2, a)) => (Semigroup[K].combine(k1, k2), f(a)) }) - - } - - implicit def catsDataEqForZipMap[K: Eq, A: Eq]: Eq[ZipMap[K, A]] = Eq.by(_.value) -} diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index bf3dc562b3..4ef2fd5285 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -64,19 +64,6 @@ trait ParallelInstances extends ParallelInstances1 { λ[Vector ~> ZipVector](v => new ZipVector(v)) } - implicit def catsStdNonEmptyParallelForZipMap[K: Semigroup, A]: NonEmptyParallel[Map[K, ?], ZipMap[K, ?]] = - new NonEmptyParallel[Map[K, ?], ZipMap[K, ?]] { - - def flatMap: FlatMap[Map[K, ?]] = cats.instances.map.catsStdInstancesForMap - def apply: Apply[ZipMap[K, ?]] = ZipMap.catsDataApplyForZipMap - - def sequential: ZipMap[K, ?] ~> Map[K, ?] = - λ[ZipMap[K, ?] ~> Map[K, ?]](_.value) - - def parallel: Map[K, ?] ~> ZipMap[K, ?] = - λ[Map[K, ?] ~> ZipMap[K, ?]](v => new ZipMap(v)) - } - implicit def catsStdParallelForZipStream[A]: Parallel[Stream, ZipStream] = new Parallel[Stream, ZipStream] { From d1eeb556e41ab1819cefa86c8c1f6fc08388693a Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 31 Oct 2017 13:06:33 +0100 Subject: [PATCH 59/63] Fix the priority of OneAnd instances --- core/src/main/scala/cats/data/OneAnd.scala | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index 640f3eb4c8..2f930a872b 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -99,7 +99,7 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) { } -private[data] sealed abstract class OneAndInstances extends OneAndLowPriority4 { +private[data] sealed abstract class OneAndInstances extends OneAndLowPriority0 { implicit def catsDataParallelForOneAnd[A, M[_] : Alternative, F[_] : Alternative] (implicit P: Parallel[M, F]): Parallel[OneAnd[M, ?], OneAnd[F, ?]] = @@ -194,7 +194,7 @@ private[data] sealed abstract class OneAndInstances extends OneAndLowPriority4 { } } -private[data] sealed abstract class OneAndLowPriority0 { +private[data] sealed abstract class OneAndLowPriority4 { implicit val catsDataComonadForNonEmptyStream: Comonad[OneAnd[Stream, ?]] = new Comonad[OneAnd[Stream, ?]] { def coflatMap[A, B](fa: OneAnd[Stream, A])(f: OneAnd[Stream, A] => B): OneAnd[Stream, B] = { @@ -215,7 +215,17 @@ private[data] sealed abstract class OneAndLowPriority0 { } } -private[data] sealed abstract class OneAndLowPriority1 extends OneAndLowPriority0 { +private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority4 { + + implicit def catsDataFunctorForOneAnd[F[_]](implicit F: Functor[F]): Functor[OneAnd[F, ?]] = + new Functor[OneAnd[F, ?]] { + def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = + fa map f + } + +} + +private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority3 { implicit def catsDataApplicativeForOneAnd[F[_]](implicit F: Alternative[F]): Applicative[OneAnd[F, ?]] = new Applicative[OneAnd[F, ?]] { @@ -235,17 +245,7 @@ private[data] sealed abstract class OneAndLowPriority1 extends OneAndLowPriority } -private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority1 { - - implicit def catsDataFunctorForOneAnd[F[_]](implicit F: Functor[F]): Functor[OneAnd[F, ?]] = - new Functor[OneAnd[F, ?]] { - def map[A, B](fa: OneAnd[F, A])(f: A => B): OneAnd[F, B] = - fa map f - } - -} - -private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority2 { +private[data] sealed abstract class OneAndLowPriority1 extends OneAndLowPriority2 { implicit def catsDataTraverseForOneAnd[F[_]](implicit F: Traverse[F]): Traverse[OneAnd[F, ?]] = new Traverse[OneAnd[F, ?]] { @@ -264,7 +264,7 @@ private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority } -private[data] sealed abstract class OneAndLowPriority4 extends OneAndLowPriority3 { +private[data] sealed abstract class OneAndLowPriority0 extends OneAndLowPriority1 { implicit def catsDataNonEmptyTraverseForOneAnd[F[_]](implicit F: Traverse[F], F2: Alternative[F]): NonEmptyTraverse[OneAnd[F, ?]] = new NonEmptyReducible[OneAnd[F, ?], F] with NonEmptyTraverse[OneAnd[F, ?]] { From 9114b2a2a6e4d14b465968df67ac0ae12f063b49 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 6 Nov 2017 17:04:02 +0100 Subject: [PATCH 60/63] Rename Parallel tests --- ...arallelTests.scala => ParallelSuite.scala} | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) rename tests/src/test/scala/cats/tests/{ParallelTests.scala => ParallelSuite.scala} (88%) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelSuite.scala similarity index 88% rename from tests/src/test/scala/cats/tests/ParallelTests.scala rename to tests/src/test/scala/cats/tests/ParallelSuite.scala index 6b7be1ef49..e1b38f2a29 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelSuite.scala @@ -6,13 +6,13 @@ import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data._ import cats.tests.CatsSuite import org.scalatest.FunSuite -import cats.laws.discipline.{ApplicativeErrorTests, NonEmptyParallelTests, SerializableTests, ParallelTests => ParallelTypeclassTests} +import cats.laws.discipline.{ApplicativeErrorTests, NonEmptyParallelTests, SerializableTests, ParallelTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary import org.typelevel.discipline.scalatest.Discipline -class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { +class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { test("ParTraversing Either should accumulate errors") { @@ -153,21 +153,21 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } } - checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?]].parallel[Int, String]) - checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?]].parallel[Int, String]) - checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?]].parallel[Int, String]) - checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]].parallel[Int, String]) - checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?]].parallel[Int, String]) + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTests[Either[String, ?], Validated[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]) + checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?]].parallel[Int, String]) checkAll("NonEmptyParallel[Vector, ZipVector]", NonEmptyParallelTests[Vector, ZipVector].nonEmptyParallel[Int, String]) checkAll("NonEmptyParallel[List, ZipList]", NonEmptyParallelTests[List, ZipList].nonEmptyParallel[Int, String]) // Can't test Parallel here, as Applicative[ZipStream].pure doesn't terminate checkAll("Parallel[Stream, ZipStream]", NonEmptyParallelTests[Stream, ZipStream].nonEmptyParallel[Int, String]) checkAll("NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector]", NonEmptyParallelTests[NonEmptyVector, ZipNonEmptyVector].nonEmptyParallel[Int, String]) checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", NonEmptyParallelTests[NonEmptyList, ZipNonEmptyList].nonEmptyParallel[Int, String]) - checkAll("Parallel[NonEmptyStream, OneAnd[ZipStream, ?]", ParallelTypeclassTests[NonEmptyStream, OneAnd[ZipStream, ?]].parallel[Int, String]) + checkAll("Parallel[NonEmptyStream, OneAnd[ZipStream, ?]", ParallelTests[NonEmptyStream, OneAnd[ZipStream, ?]].parallel[Int, String]) - checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id].parallel[Int, String]) + checkAll("Parallel[Id, Id]", ParallelTests[Id, Id].parallel[Int, String]) checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", SerializableTests.serializable(NonEmptyParallel[NonEmptyList, ZipNonEmptyList])) checkAll("Parallel[Either[String, ?], Validated[String, ?]]", SerializableTests.serializable(Parallel[Either[String, ?], Validated[String, ?]])) @@ -176,7 +176,7 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) - checkAll("Parallel[KlesliT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?]].parallel[Int, String]) + checkAll("Parallel[KlesliT[M, ?], Nested[F, Option, ?]]", ParallelTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?]].parallel[Int, String]) } From 8bfec4b196bfcc09e0a4f01b4bcb485e4bf6047c Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 6 Nov 2017 17:04:02 +0100 Subject: [PATCH 61/63] Rename Parallel tests --- ...arallelTests.scala => ParallelSuite.scala} | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) rename tests/src/test/scala/cats/tests/{ParallelTests.scala => ParallelSuite.scala} (88%) diff --git a/tests/src/test/scala/cats/tests/ParallelTests.scala b/tests/src/test/scala/cats/tests/ParallelSuite.scala similarity index 88% rename from tests/src/test/scala/cats/tests/ParallelTests.scala rename to tests/src/test/scala/cats/tests/ParallelSuite.scala index 6b7be1ef49..a2f57018b9 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelSuite.scala @@ -1,4 +1,4 @@ -package cats +package cats.tests import cats.data.NonEmptyList.ZipNonEmptyList @@ -6,13 +6,13 @@ import cats.data.NonEmptyVector.ZipNonEmptyVector import cats.data._ import cats.tests.CatsSuite import org.scalatest.FunSuite -import cats.laws.discipline.{ApplicativeErrorTests, NonEmptyParallelTests, SerializableTests, ParallelTests => ParallelTypeclassTests} +import cats.laws.discipline.{ApplicativeErrorTests, NonEmptyParallelTests, SerializableTests, ParallelTests} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary import org.typelevel.discipline.scalatest.Discipline -class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { +class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { test("ParTraversing Either should accumulate errors") { @@ -153,21 +153,21 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { } } - checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTypeclassTests[Either[String, ?], Validated[String, ?]].parallel[Int, String]) - checkAll("Parallel[OptionT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[OptionT[Either[String, ?], ?], Nested[Validated[String, ?], Option, ?]].parallel[Int, String]) - checkAll("Parallel[EitherT[M, String, ?], Nested[F, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Either[String, ?], String, ?], Nested[Validated[String, ?], Validated[String, ?], ?]].parallel[Int, String]) - checkAll("Parallel[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]]", ParallelTypeclassTests[EitherT[Option, String, ?], Nested[Option, Validated[String, ?], ?]].parallel[Int, String]) - checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTypeclassTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?]].parallel[Int, String]) + checkAll("Parallel[Either[String, ?], Validated[String, ?]]", ParallelTests[Either[String, ?], Validated[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]) + checkAll("Parallel[WriterT[M, Int, ?], WriterT[F, Int, ?]]", ParallelTests[WriterT[Either[String, ?], Int, ?], WriterT[Validated[String, ?], Int, ?]].parallel[Int, String]) checkAll("NonEmptyParallel[Vector, ZipVector]", NonEmptyParallelTests[Vector, ZipVector].nonEmptyParallel[Int, String]) checkAll("NonEmptyParallel[List, ZipList]", NonEmptyParallelTests[List, ZipList].nonEmptyParallel[Int, String]) // Can't test Parallel here, as Applicative[ZipStream].pure doesn't terminate checkAll("Parallel[Stream, ZipStream]", NonEmptyParallelTests[Stream, ZipStream].nonEmptyParallel[Int, String]) checkAll("NonEmptyParallel[NonEmptyVector, ZipNonEmptyVector]", NonEmptyParallelTests[NonEmptyVector, ZipNonEmptyVector].nonEmptyParallel[Int, String]) checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", NonEmptyParallelTests[NonEmptyList, ZipNonEmptyList].nonEmptyParallel[Int, String]) - checkAll("Parallel[NonEmptyStream, OneAnd[ZipStream, ?]", ParallelTypeclassTests[NonEmptyStream, OneAnd[ZipStream, ?]].parallel[Int, String]) + checkAll("Parallel[NonEmptyStream, OneAnd[ZipStream, ?]", ParallelTests[NonEmptyStream, OneAnd[ZipStream, ?]].parallel[Int, String]) - checkAll("Parallel[Id, Id]", ParallelTypeclassTests[Id, Id].parallel[Int, String]) + checkAll("Parallel[Id, Id]", ParallelTests[Id, Id].parallel[Int, String]) checkAll("NonEmptyParallel[NonEmptyList, ZipNonEmptyList]", SerializableTests.serializable(NonEmptyParallel[NonEmptyList, ZipNonEmptyList])) checkAll("Parallel[Either[String, ?], Validated[String, ?]]", SerializableTests.serializable(Parallel[Either[String, ?], Validated[String, ?]])) @@ -176,7 +176,7 @@ class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { implicit def kleisliEq[F[_], A, B](implicit A: Arbitrary[A], FB: Eq[F[B]]): Eq[Kleisli[F, A, B]] = Eq.by[Kleisli[F, A, B], A => F[B]](_.run) - checkAll("Parallel[KlesliT[M, ?], Nested[F, Option, ?]]", ParallelTypeclassTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?]].parallel[Int, String]) + checkAll("Parallel[KlesliT[M, ?], Nested[F, Option, ?]]", ParallelTests[Kleisli[Either[String, ?], Int, ?], Kleisli[Validated[String, ?], Int, ?]].parallel[Int, String]) } From 0e752e3e3bdef4c3c9e05f319ff53bcc309776a9 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 10 Nov 2017 17:09:37 +0100 Subject: [PATCH 62/63] Add mima exceptions --- build.sbt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 9ed50b92be..865f00bf19 100644 --- a/build.sbt +++ b/build.sbt @@ -190,10 +190,21 @@ lazy val binaryCompatibleVersion = "1.0.0-RC1" def mimaSettings(moduleName: String) = Seq( mimaPreviousArtifacts := Set("org.typelevel" %% moduleName % binaryCompatibleVersion), // TODO: remove this post-release of 1.0.0 - mimaBinaryIssueFilters += { + mimaBinaryIssueFilters ++= { import com.typesafe.tools.mima.core._ import com.typesafe.tools.mima.core.ProblemFilters._ - exclude[ReversedMissingMethodProblem]("cats.syntax.FoldableSyntax.catsSyntaxFoldOps") + Seq( + exclude[ReversedMissingMethodProblem]("cats.syntax.FoldableSyntax.catsSyntaxFoldOps"), + exclude[MissingTypesProblem]("cats.data.OneAndLowPriority3"), + exclude[MissingTypesProblem]("cats.data.OneAndLowPriority2"), + exclude[MissingTypesProblem]("cats.data.OneAndLowPriority1"), + exclude[DirectMissingMethodProblem]("cats.data.OneAndLowPriority3.catsDataNonEmptyTraverseForOneAnd"), + exclude[DirectMissingMethodProblem]("cats.data.OneAndLowPriority2.catsDataTraverseForOneAnd"), + exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdNonEmptyParallelForZipVector"), + exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdParallelForZipStream"), + exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdNonEmptyParallelForZipList"), + exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdParallelForFailFastFuture") + ) } ) From 2ca66c986dd329d7c55d866f6d7459f5421ec57e Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 14 Nov 2017 13:40:15 +0100 Subject: [PATCH 63/63] Remove fail fast future --- .../main/scala/cats/data/FailFastFuture.scala | 35 --------------- .../main/scala/cats/instances/parallel.scala | 14 ------ .../test/scala/cats/tests/FutureTests.scala | 8 ---- .../test/scala/cats/tests/FutureSuite.scala | 45 +------------------ 4 files changed, 1 insertion(+), 101 deletions(-) delete mode 100644 core/src/main/scala/cats/data/FailFastFuture.scala diff --git a/core/src/main/scala/cats/data/FailFastFuture.scala b/core/src/main/scala/cats/data/FailFastFuture.scala deleted file mode 100644 index 0237174025..0000000000 --- a/core/src/main/scala/cats/data/FailFastFuture.scala +++ /dev/null @@ -1,35 +0,0 @@ -package cats.data - -import cats.Applicative - -import scala.concurrent.{ExecutionContext, Future, Promise} -import scala.util.{Failure, Success} - -class FailFastFuture[A](val value: Future[A]) extends AnyVal - -object FailFastFuture { - - def apply[A](value: Future[A]): FailFastFuture[A] = new FailFastFuture(value) - - def catsDataApplicativeForFailFastFuture(implicit ec: ExecutionContext): Applicative[FailFastFuture] = - new Applicative[FailFastFuture] { - override def pure[A](x: A): FailFastFuture[A] = FailFastFuture(Future.successful(x)) - - override def ap[A, B](ff: FailFastFuture[(A) => B])(fa: FailFastFuture[A]): FailFastFuture[B] = { - val p = Promise[B]() - - ff.value.onComplete { - case Failure(t) => p.tryFailure(t) - case Success(_) => () - } - - fa.value.onComplete { - case Failure(t) => p.tryFailure(t) - case Success(_) => () - } - - p.tryCompleteWith(ff.value.flatMap(f => fa.value.map(f))) - FailFastFuture(p.future) - } - } -} diff --git a/core/src/main/scala/cats/instances/parallel.scala b/core/src/main/scala/cats/instances/parallel.scala index 1364767676..6200c573e6 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -5,7 +5,6 @@ import cats.kernel.Semigroup import cats.syntax.either._ import cats.{Applicative, Apply, FlatMap, Functor, Monad, NonEmptyParallel, Parallel, ~>} -import scala.concurrent.{ExecutionContext, Future} trait ParallelInstances extends ParallelInstances1 { implicit def catsParallelForEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { @@ -77,19 +76,6 @@ trait ParallelInstances extends ParallelInstances1 { λ[Stream ~> ZipStream](v => new ZipStream(v)) } - implicit def catsStdParallelForFailFastFuture[A](implicit ec: ExecutionContext): Parallel[Future, FailFastFuture] = - new Parallel[Future, FailFastFuture] { - - def monad: Monad[Future] = cats.instances.future.catsStdInstancesForFuture - def applicative: Applicative[FailFastFuture] = FailFastFuture.catsDataApplicativeForFailFastFuture - - def sequential: FailFastFuture ~> Future = - λ[FailFastFuture ~> Future](_.value) - - def parallel: Future ~> FailFastFuture = - λ[Future ~> FailFastFuture](f => FailFastFuture(f)) - } - implicit def catsParallelForEitherTNestedParallelValidated[F[_], M[_], E: Semigroup] (implicit P: Parallel[M, F]): Parallel[EitherT[M, E, ?], Nested[F, Validated[E, ?], ?]] = diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 61183f19ec..bc712666c1 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -2,7 +2,6 @@ package cats package js package tests -import cats.data.FailFastFuture import cats.kernel.laws.discipline.{MonoidTests => MonoidLawTests, SemigroupTests => SemigroupLawTests} import cats.laws.discipline._ import cats.js.instances.Await @@ -38,12 +37,6 @@ class FutureTests extends CatsSuite { } } - implicit def eqffa[A: Eq]: Eq[FailFastFuture[A]] = - Eq.by(_.value) - - - implicit def failFastArbitrary[A: Arbitrary]: Arbitrary[FailFastFuture[A]] = - Arbitrary(implicitly[Arbitrary[Future[A]]].arbitrary.map(FailFastFuture.apply)) implicit val throwableEq: Eq[Throwable] = Eq.by[Throwable, String](_.toString) @@ -63,7 +56,6 @@ class FutureTests extends CatsSuite { checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) - checkAll("Parallel[Future, FailFastFuture]", ParallelTests[Future, FailFastFuture].parallel[Int, String]) { implicit val F = ListWrapper.semigroup[Int] diff --git a/jvm/src/test/scala/cats/tests/FutureSuite.scala b/jvm/src/test/scala/cats/tests/FutureSuite.scala index 35b1152d09..02d477cc91 100644 --- a/jvm/src/test/scala/cats/tests/FutureSuite.scala +++ b/jvm/src/test/scala/cats/tests/FutureSuite.scala @@ -3,20 +3,17 @@ package jvm package tests import cats.kernel.laws.discipline.{MonoidTests => MonoidLawTests, SemigroupTests => SemigroupLawTests} -import cats.data.FailFastFuture import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ import cats.tests.{CatsSuite, ListWrapper} -import scala.concurrent.{Await, Future, blocking} +import scala.concurrent.{Await, Future} import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Arbitrary.arbitrary import org.scalacheck.rng.Seed -import scala.util.{Try, Failure} - class FutureSuite extends CatsSuite { val timeout = 3.seconds @@ -31,14 +28,9 @@ class FutureSuite extends CatsSuite { } } - implicit def eqffa[A: Eq]: Eq[FailFastFuture[A]] = - Eq.by(_.value) - implicit def cogen[A: Cogen]: Cogen[Future[A]] = Cogen[Future[A]] { (seed: Seed, t: Future[A]) => Cogen[A].perturb(seed, Await.result(t, timeout)) } - implicit def failFastArbitrary[A: Arbitrary]: Arbitrary[FailFastFuture[A]] = - Arbitrary(implicitly[Arbitrary[Future[A]]].arbitrary.map(FailFastFuture.apply)) implicit val throwableEq: Eq[Throwable] = Eq.by[Throwable, String](_.toString) @@ -47,44 +39,9 @@ class FutureSuite extends CatsSuite { implicit val nonFatalArbitrary: Arbitrary[Throwable] = Arbitrary(arbitrary[Exception].map(identity)) - test("FailFastFuture should fail fast on right side") { - val exA = new Exception("A") - val exB = new Exception("B") - val fa: Future[Int] = Future { - blocking(Thread.sleep(200)) - throw exA - } - - val fb: Future[Int] = Future { - blocking(Thread.sleep(1)) - throw exB - } - - val fab: Future[Int] = (fa, fb).parMapN(_ + _) - Try(Await.result(fab, timeout)) should === (Failure(exB)) - } - - test("FailFastFuture should fail fast on left side") { - val exA = new Exception("A") - val exB = new Exception("B") - val fa: Future[Int] = Future { - blocking(Thread.sleep(1)) - throw exA - } - - val fb: Future[Int] = Future { - blocking(Thread.sleep(200)) - throw exB - } - - val fab: Future[Int] = (fa, fb).parMapN(_ + _) - Try(Await.result(fab, timeout)) should === (Failure(exA)) - } - checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) checkAll("Future", CoflatMapTests[Future].coflatMap[Int, Int, Int]) - checkAll("Parallel[Future, FailFastFuture]", ParallelTests[Future, FailFastFuture].parallel[Int, String]) { implicit val F = ListWrapper.semigroup[Int]