From ef64ff8d4e8f186c7cc7564d37eb734273cd70fc Mon Sep 17 00:00:00 2001 From: LukaJCB Date: Fri, 17 Nov 2017 17:43:31 -0800 Subject: [PATCH] Add more Parallel instances (#1938) * Initial version of Parallel * Add Either/Validated Parallel instance * Break up syntax for parallel * Add Parallel syntax tests * Add Tuple syntax for Parallel * Add ParallelTests * Fix Parallel law * Add more tests * Add Parallel Kleisli instance * Add instances for OptionT and EitherT to nested * Add law tests for parallel OptionT and EitherT * Make EitherT instance forward to Validated * Add Kleisli lawTest * Add WriterT instance * Add more tests * Add scaladoc * Add ApplicativeError instance for MonadError and Parallel * Add Test that actually hits the implicated ApplicativeError instance * Fix mixup * Move appError instance to Parallel companion object * Fix apperror test * Add sequential roundtrip law * Add ZipNEL and ZipNEV and Parallel instances * Add law for testing that pure is consistent across Parallel pairs * Add Parallel Serializable tests * Add EitherT Parallel instance that doesn't require a Parallel Instance for M * Add ZipVector + Parallel instance * Add ZipVector test * Add scaladoc to ApplicativeError and change order of type parameters * Add Parallel#identity function * Add identity instances for Future and Id * Simplify parAp2 implementation * Refactor Parallel * Add applicativeError instace method * Reverse applicativeError and remove redundant .apply * Simplify further * Add FailFastFuture + Parallel instance * Shorten wait times * Add ZipStream and OneAnd instance * Convert traits to abstract classes * Add consistency test for zip stream * Add Applicative test for Applicative[OneAnd] * Add parAp test * Add ZipList and lawtest all Zip* instances * Add ZipList consistency test * Add NonEmptyParallel * Add test cases for ParNonEmptyTraverse * Update scaladoc * Remove FailFastFuture and all Zip* instances * Rename methods in NonEmptyParallel * optimize AppError * Add parFlatTraverse and sequence * Revert "Remove FailFastFuture and all Zip* instances" This reverts commit c5e34236ff0ecde627c6104a96b30d632f8ce03e. * Add parFlatTraverse and sequence * Add isomorphic functor law * Add ZipMap? * Fix law test parameters * Remove ZipMap * Fix the priority of OneAnd instances * Rename Parallel tests * Rename Parallel tests * Add mima exceptions * Remove fail fast future --- build.sbt | 9 +++ .../main/scala/cats/data/NonEmptyList.scala | 36 +++++++++ .../main/scala/cats/data/NonEmptyVector.scala | 33 ++++++++ core/src/main/scala/cats/data/OneAnd.scala | 45 +++++++++-- core/src/main/scala/cats/data/ZipList.scala | 26 ++++++ core/src/main/scala/cats/data/ZipStream.scala | 32 ++++++++ core/src/main/scala/cats/data/ZipVector.scala | 22 +++++ .../main/scala/cats/instances/parallel.scala | 42 +++++++++- .../test/scala/cats/tests/FutureTests.scala | 1 + .../test/scala/cats/tests/FutureSuite.scala | 1 + .../cats/laws/discipline/Arbitrary.scala | 17 ++++ .../src/test/scala/cats/tests/ListSuite.scala | 6 +- .../scala/cats/tests/NonEmptyListSuite.scala | 5 +- .../cats/tests/NonEmptyVectorSuite.scala | 6 +- .../test/scala/cats/tests/OneAndSuite.scala | 8 +- ...arallelTests.scala => ParallelSuite.scala} | 81 ++++++++++++++++--- .../test/scala/cats/tests/StreamSuite.scala | 8 +- .../test/scala/cats/tests/VectorSuite.scala | 6 +- 18 files changed, 357 insertions(+), 27 deletions(-) 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 rename tests/src/test/scala/cats/tests/{ParallelTests.scala => ParallelSuite.scala} (58%) diff --git a/build.sbt b/build.sbt index f68e160395..6cf03fd19b 100644 --- a/build.sbt +++ b/build.sbt @@ -195,6 +195,15 @@ def mimaSettings(moduleName: String) = Seq( import com.typesafe.tools.mima.core.ProblemFilters._ 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"), exclude[DirectMissingMethodProblem]("cats.data.EitherTInstances2.catsDataMonadErrorForEitherT") ) } diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index ef24c925c2..36620690b8 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._ import scala.annotation.tailrec @@ -421,6 +422,27 @@ 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 catsDataCommutativeApplyForZipNonEmptyList: CommutativeApply[ZipNonEmptyList] = + new CommutativeApply[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 { @@ -537,6 +559,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.catsDataCommutativeApplyForZipNonEmptyList + + 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 368fa8c95c..038a6268f0 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._ @@ -353,6 +354,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.catsDataCommutativeApplyForZipNonEmptyVector + def flatMap: FlatMap[NonEmptyVector] = NonEmptyVector.catsDataInstancesForNonEmptyVector + + def sequential: ZipNonEmptyVector ~> NonEmptyVector = + λ[ZipNonEmptyVector ~> NonEmptyVector](_.value) + + def parallel: NonEmptyVector ~> ZipNonEmptyVector = + λ[NonEmptyVector ~> ZipNonEmptyVector](nev => new ZipNonEmptyVector(nev)) + } } @@ -379,5 +392,25 @@ 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 catsDataCommutativeApplyForZipNonEmptyVector: CommutativeApply[ZipNonEmptyVector] = + new CommutativeApply[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 0bbd9a6d97..e5052a6761 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -105,8 +105,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 OneAndLowPriority0 { + 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]]{ @@ -186,7 +200,7 @@ private[data] sealed abstract class OneAndInstances extends OneAndLowPriority3 { } } -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] = { @@ -207,8 +221,7 @@ 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, ?]] { @@ -218,7 +231,27 @@ private[data] sealed abstract class OneAndLowPriority1 extends OneAndLowPriority } -private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority1 { +private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority3 { + + 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 OneAndLowPriority1 extends OneAndLowPriority2 { implicit def catsDataTraverseForOneAnd[F[_]](implicit F: Traverse[F]): Traverse[OneAnd[F, ?]] = new Traverse[OneAnd[F, ?]] { @@ -237,7 +270,7 @@ private[data] sealed abstract class OneAndLowPriority2 extends OneAndLowPriority } -private[data] sealed abstract class OneAndLowPriority3 extends OneAndLowPriority2 { +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, ?]] { 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..603ab1acdb --- /dev/null +++ b/core/src/main/scala/cats/data/ZipList.scala @@ -0,0 +1,26 @@ +package cats.data + +import cats.{CommutativeApply, 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 catsDataCommutativeApplyForZipList: CommutativeApply[ZipList] = new CommutativeApply[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..9c3861a828 --- /dev/null +++ b/core/src/main/scala/cats/data/ZipStream.scala @@ -0,0 +1,32 @@ +package cats.data + +import cats.{Alternative, CommutativeApplicative, 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] with CommutativeApplicative[ZipStream] = + new Alternative[ZipStream] with CommutativeApplicative[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..74a0529adb --- /dev/null +++ b/core/src/main/scala/cats/data/ZipVector.scala @@ -0,0 +1,22 @@ +package cats.data + +import cats.{CommutativeApply, 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 catsDataCommutativeApplyForZipVector: CommutativeApply[ZipVector] = new CommutativeApply[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..6200c573e6 100644 --- a/core/src/main/scala/cats/instances/parallel.scala +++ b/core/src/main/scala/cats/instances/parallel.scala @@ -3,7 +3,8 @@ 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, ~>} + trait ParallelInstances extends ParallelInstances1 { implicit def catsParallelForEitherValidated[E: Semigroup]: Parallel[Either[E, ?], Validated[E, ?]] = new Parallel[Either[E, ?], Validated[E, ?]] { @@ -36,6 +37,45 @@ 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.catsDataCommutativeApplyForZipList + + 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.catsDataCommutativeApplyForZipVector + + 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 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 7cac7bfefb..bc712666c1 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -37,6 +37,7 @@ class FutureTests extends CatsSuite { } } + implicit val throwableEq: Eq[Throwable] = Eq.by[Throwable, String](_.toString) diff --git a/jvm/src/test/scala/cats/tests/FutureSuite.scala b/jvm/src/test/scala/cats/tests/FutureSuite.scala index d60a73f1f5..02d477cc91 100644 --- a/jvm/src/test/scala/cats/tests/FutureSuite.scala +++ b/jvm/src/test/scala/cats/tests/FutureSuite.scala @@ -31,6 +31,7 @@ class FutureSuite extends CatsSuite { 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 val throwableEq: Eq[Throwable] = Eq.by[Throwable, String](_.toString) diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index f054efeeb2..eadc26eb9d 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 scala.collection.immutable.{SortedMap, SortedSet} import cats.data._ @@ -49,6 +51,18 @@ 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)))) @@ -56,6 +70,9 @@ object arbitrary extends ArbitraryInstances0 { 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/ListSuite.scala b/tests/src/test/scala/cats/tests/ListSuite.scala index 8f881438f2..30ce059048 100644 --- a/tests/src/test/scala/cats/tests/ListSuite.scala +++ b/tests/src/test/scala/cats/tests/ListSuite.scala @@ -1,8 +1,8 @@ package cats package tests -import cats.data.NonEmptyList -import cats.laws.discipline.{TraverseTests, CoflatMapTests, AlternativeTests, SerializableTests, SemigroupalTests} +import cats.data.{NonEmptyList, ZipList} +import cats.laws.discipline.{CommutativeApplyTests, TraverseTests, CoflatMapTests, AlternativeTests, SerializableTests, SemigroupalTests} import cats.laws.discipline.arbitrary._ class ListSuite extends CatsSuite { @@ -19,6 +19,8 @@ class ListSuite 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]", CommutativeApplyTests[ZipList].commutativeApply[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/NonEmptyListSuite.scala b/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala index b863d4c61f..a4fd14f36f 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyListSuite.scala @@ -4,8 +4,9 @@ package tests import cats.kernel.laws.discipline.{SemigroupTests, OrderTests, PartialOrderTests, EqTests} import cats.data.{NonEmptyList, NonEmptyVector} +import cats.data.NonEmptyList.ZipNonEmptyList import cats.laws.discipline.arbitrary._ -import cats.laws.discipline.{ComonadTests, NonEmptyTraverseTests, MonadTests, ReducibleTests, SemigroupKTests, SerializableTests} +import cats.laws.discipline.{CommutativeApplyTests, ComonadTests, NonEmptyTraverseTests, MonadTests, ReducibleTests, SemigroupKTests, SerializableTests} class NonEmptyListSuite extends CatsSuite { // Lots of collections here.. telling ScalaCheck to calm down a bit @@ -35,6 +36,8 @@ class NonEmptyListSuite extends CatsSuite { checkAll("NonEmptyList[ListWrapper[Int]]", EqTests[NonEmptyList[ListWrapper[Int]]].eqv) checkAll("Eq[NonEmptyList[ListWrapper[Int]]]", SerializableTests.serializable(Eq[NonEmptyList[ListWrapper[Int]]])) + checkAll("ZipNonEmptyList[Int]", CommutativeApplyTests[ZipNonEmptyList].commutativeApply[Int, Int, Int]) + { implicit val A = ListWrapper.partialOrder[Int] checkAll("NonEmptyList[ListWrapper[Int]]", PartialOrderTests[NonEmptyList[ListWrapper[Int]]].partialOrder) diff --git a/tests/src/test/scala/cats/tests/NonEmptyVectorSuite.scala b/tests/src/test/scala/cats/tests/NonEmptyVectorSuite.scala index 17b5d6c072..7554b7664a 100644 --- a/tests/src/test/scala/cats/tests/NonEmptyVectorSuite.scala +++ b/tests/src/test/scala/cats/tests/NonEmptyVectorSuite.scala @@ -3,10 +3,12 @@ package tests import catalysts.Platform +import cats.data.NonEmptyVector.ZipNonEmptyVector + import cats.kernel.laws.discipline.{SemigroupTests, EqTests} import cats.data.NonEmptyVector -import cats.laws.discipline.{ComonadTests, SemigroupKTests, FoldableTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests, MonadTests} +import cats.laws.discipline.{CommutativeApplyTests, ComonadTests, SemigroupKTests, FoldableTests, SerializableTests, NonEmptyTraverseTests, ReducibleTests, MonadTests} import cats.laws.discipline.arbitrary._ import scala.util.Properties @@ -38,6 +40,8 @@ class NonEmptyVectorSuite extends CatsSuite { checkAll("Foldable[NonEmptyVector]", SerializableTests.serializable(Foldable[NonEmptyVector])) + checkAll("ZipNonEmptyVector[Int]", CommutativeApplyTests[ZipNonEmptyVector].commutativeApply[Int, Int, Int]) + checkAll("CommutativeApply[ZipNonEmptyVector]", SerializableTests.serializable(CommutativeApply[ZipNonEmptyVector])) // Test functor and subclasses don't have implicit conflicts implicitly[Functor[NonEmptyVector]] diff --git a/tests/src/test/scala/cats/tests/OneAndSuite.scala b/tests/src/test/scala/cats/tests/OneAndSuite.scala index 57f7d2479d..04bb440f08 100644 --- a/tests/src/test/scala/cats/tests/OneAndSuite.scala +++ b/tests/src/test/scala/cats/tests/OneAndSuite.scala @@ -5,7 +5,7 @@ import cats.kernel.laws.discipline.{SemigroupTests, EqTests} import cats.instances.stream._ import cats.data.{NonEmptyStream, OneAnd} -import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, SemigroupalTests, TraverseTests, NonEmptyTraverseTests, ReducibleTests} +import cats.laws.discipline.{ApplicativeTests, SemigroupalTests, ComonadTests, FoldableTests, FunctorTests, MonadTests, NonEmptyTraverseTests, ReducibleTests, SemigroupKTests, SerializableTests, TraverseTests} import cats.laws.discipline.arbitrary._ class OneAndSuite extends CatsSuite { @@ -37,6 +37,12 @@ class OneAndSuite 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/ParallelSuite.scala similarity index 58% rename from tests/src/test/scala/cats/tests/ParallelTests.scala rename to tests/src/test/scala/cats/tests/ParallelSuite.scala index d2c31752a3..dc4117a2d4 100644 --- a/tests/src/test/scala/cats/tests/ParallelTests.scala +++ b/tests/src/test/scala/cats/tests/ParallelSuite.scala @@ -1,17 +1,18 @@ -package cats - +package cats.tests +import 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} import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ import org.scalacheck.Arbitrary import org.typelevel.discipline.scalatest.Discipline import scala.collection.immutable.SortedSet -class ParallelTests extends CatsSuite with ApplicativeErrorForEitherTest { +class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { test("ParTraversing Either should accumulate errors") { @@ -104,22 +105,78 @@ 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, ?]]", 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, ?]", ParallelTests[NonEmptyStream, OneAnd[ZipStream, ?]].parallel[Int, String]) - 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].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, ?]])) { 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]) } diff --git a/tests/src/test/scala/cats/tests/StreamSuite.scala b/tests/src/test/scala/cats/tests/StreamSuite.scala index 648f114f6e..84d65f71cf 100644 --- a/tests/src/test/scala/cats/tests/StreamSuite.scala +++ b/tests/src/test/scala/cats/tests/StreamSuite.scala @@ -1,7 +1,10 @@ package cats package tests -import cats.laws.discipline.{CoflatMapTests, MonadTests, AlternativeTests, SerializableTests, TraverseTests, SemigroupalTests} +import cats.laws.discipline.{CommutativeApplyTests, CoflatMapTests, MonadTests, AlternativeTests, SerializableTests, TraverseTests, SemigroupalTests} +import cats.data.ZipStream + +import cats.laws.discipline.arbitrary._ class StreamSuite extends CatsSuite { checkAll("Stream[Int]", SemigroupalTests[Stream].semigroupal[Int, Int, Int]) @@ -19,6 +22,9 @@ class StreamSuite 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]", CommutativeApplyTests[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/VectorSuite.scala b/tests/src/test/scala/cats/tests/VectorSuite.scala index 9c9137cfd5..c8b2a89804 100644 --- a/tests/src/test/scala/cats/tests/VectorSuite.scala +++ b/tests/src/test/scala/cats/tests/VectorSuite.scala @@ -1,8 +1,8 @@ package cats package tests -import cats.data.NonEmptyVector -import cats.laws.discipline.{AlternativeTests, CoflatMapTests, SerializableTests, TraverseTests, SemigroupalTests} +import cats.data.{NonEmptyVector, ZipVector} +import cats.laws.discipline.{CommutativeApplyTests, AlternativeTests, CoflatMapTests, SerializableTests, TraverseTests, SemigroupalTests} import cats.laws.discipline.arbitrary._ class VectorSuite extends CatsSuite { @@ -18,6 +18,8 @@ class VectorSuite 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]", CommutativeApplyTests[ZipVector].commutativeApply[Int, Int, Int]) + test("show") { Vector(1, 2, 3).show should === ("Vector(1, 2, 3)")