From b311ab9ce9957c65e9806376a895cc1c9b78fa94 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 2 Sep 2017 12:42:58 +0200 Subject: [PATCH] Add ZipNEL and ZipNEV and Parallel instances --- .../main/scala/cats/data/NonEmptyList.scala | 27 +++++++++++++++++++ .../main/scala/cats/data/NonEmptyVector.scala | 27 +++++++++++++++++++ .../cats/laws/discipline/Arbitrary.scala | 14 ++++++++++ .../test/scala/cats/tests/ParallelTests.scala | 16 +++++++++++ 4 files changed, 84 insertions(+) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 3c86a0046fa..f18ce01a321 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,20 @@ 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) + implicit def zipNelShow[A](implicit ev: Show[A]): Show[ZipNonEmptyList[A]] = + Show.show(a => s"ZipList(${Show[NonEmptyList[A]].show(a.value)})") + } } private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 { @@ -479,6 +494,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 49d04d9ef6b..81c20573e2a 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,18 @@ 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) + implicit def zipNevShow[A](implicit ev: Show[A]): Show[ZipNonEmptyVector[A]] = + Show.show(a => s"ZipVector(${Show[NonEmptyVector[A]].show(a.value)})") + } } diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index 963d71faa2e..e593ba6bdc3 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,24 @@ 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 catsLawsCogenForZipNonEmptyVector[A](implicit A: Cogen[A]): Cogen[ZipNonEmptyVector[A]] = + Cogen[NonEmptyVector[A]].contramap(_.value) + 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 catsLawsCogenForZipNonEmptyList[A](implicit A: Cogen[A]): Cogen[ZipNonEmptyList[A]] = + Cogen[NonEmptyList[A]].contramap(_.value) + 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 328c3e2d8f3..a8683ddebd7 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]] =