diff --git a/build.sbt b/build.sbt index 342b0965ba..5cab6a43d7 100644 --- a/build.sbt +++ b/build.sbt @@ -195,6 +195,41 @@ def mimaSettings(moduleName: String) = Seq( import com.typesafe.tools.mima.core.ProblemFilters._ Seq( exclude[ReversedMissingMethodProblem]("cats.syntax.FoldableSyntax.catsSyntaxFoldOps"), + exclude[ReversedMissingMethodProblem]("cats.Traverse.unorderedTraverse"), + exclude[ReversedMissingMethodProblem]("cats.Traverse.unorderedSequence"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedTraverse.unorderedTraverse"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedTraverse.unorderedSequence"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + exclude[IncompatibleResultTypeProblem]("cats.implicits.catsStdInstancesForMap"), + exclude[IncompatibleResultTypeProblem]("cats.implicits.catsStdInstancesForSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.unorderedFoldMap"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedTraverse#Ops.unorderedTraverse"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedTraverse#Ops.unorderedSequence"), + exclude[UpdateForwarderBodyProblem]("cats.Foldable.size"), + exclude[ReversedMissingMethodProblem]("cats.Foldable.unorderedFold"), + exclude[ReversedMissingMethodProblem]("cats.Foldable.unorderedFoldMap"), + exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.size"), + exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.forall"), + exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.isEmpty"), + exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.nonEmpty"), + exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.exists"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#ToUnorderedFoldableOps.toUnorderedFoldableOps"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#ToUnorderedFoldableOps.toUnorderedFoldableOps"), + exclude[IncompatibleResultTypeProblem]("cats.instances.SetInstances.catsStdInstancesForSet"), + exclude[ReversedMissingMethodProblem]("cats.instances.SetInstances.cats$instances$SetInstances$_setter_$catsStdInstancesForSet_="), + exclude[ReversedMissingMethodProblem]("cats.instances.SetInstances.catsStdInstancesForSet"), + exclude[DirectMissingMethodProblem]("cats.instances.MapInstances.catsStdInstancesForMap"), + exclude[ReversedMissingMethodProblem]("cats.instances.MapInstances.catsStdInstancesForMap"), + exclude[IncompatibleResultTypeProblem]("cats.instances.package#all.catsStdInstancesForMap"), + exclude[IncompatibleResultTypeProblem]("cats.instances.package#all.catsStdInstancesForSet"), + exclude[IncompatibleResultTypeProblem]("cats.instances.package#map.catsStdInstancesForMap"), + exclude[IncompatibleResultTypeProblem]("cats.instances.package#set.catsStdInstancesForSet"), + exclude[IncompatibleResultTypeProblem]("cats.instances.MapInstances.catsStdInstancesForMap"), + exclude[DirectMissingMethodProblem]("cats.data.EitherTInstances2.catsDataMonadErrorForEitherT"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority3"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority2"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority1"), diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 4247dc2f52..2d5f931832 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -2,7 +2,7 @@ package cats import scala.collection.mutable import cats.instances.either._ -import cats.instances.long._ +import cats.kernel.CommutativeMonoid import simulacrum.typeclass import Foldable.sentinel @@ -16,6 +16,7 @@ import Foldable.sentinel * `Foldable[_]` instance. * * Instances of Foldable should be ordered collections to allow for consistent folding. + * Use the `UnorderedFoldable` type class if you want to fold over unordered collections. * * Foldable[F] is implemented in terms of two basic methods: * @@ -27,7 +28,7 @@ import Foldable.sentinel * * See: [[http://www.cs.nott.ac.uk/~pszgmh/fold.pdf A tutorial on the universality and expressiveness of fold]] */ -@typeclass trait Foldable[F[_]] { self => +@typeclass trait Foldable[F[_]] extends UnorderedFoldable[F] { self => /** * Left associative fold on 'F' using the function 'f'. @@ -55,6 +56,7 @@ import Foldable.sentinel */ def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B + /** * Right associative lazy fold on `F` using the folding function 'f'. * @@ -192,16 +194,6 @@ import Foldable.sentinel def maximumOption[A](fa: F[A])(implicit A: Order[A]): Option[A] = reduceLeftOption(fa)(A.max) - /** - * The size of this Foldable. - * - * This is overriden in structures that have more efficient size implementations - * (e.g. Vector, Set, Map). - * - * Note: will not terminate for infinite-sized collections. - */ - def size[A](fa: F[A]): Long = foldMap(fa)(_ => 1) - /** * Get the element at the index of the `Foldable`. */ @@ -390,7 +382,7 @@ import Foldable.sentinel * * If there are no elements, the result is `false`. */ - def exists[A](fa: F[A])(p: A => Boolean): Boolean = + override def exists[A](fa: F[A])(p: A => Boolean): Boolean = foldRight(fa, Eval.False) { (a, lb) => if (p(a)) Eval.True else lb }.value @@ -400,7 +392,7 @@ import Foldable.sentinel * * If there are no elements, the result is `true`. */ - def forall[A](fa: F[A])(p: A => Boolean): Boolean = + override def forall[A](fa: F[A])(p: A => Boolean): Boolean = foldRight(fa, Eval.True) { (a, lb) => if (p(a)) lb else Eval.False }.value @@ -539,10 +531,10 @@ import Foldable.sentinel /** * Returns true if there are no elements. Otherwise false. */ - def isEmpty[A](fa: F[A]): Boolean = + override def isEmpty[A](fa: F[A]): Boolean = foldRight(fa, Eval.True)((_, _) => Eval.False).value - def nonEmpty[A](fa: F[A]): Boolean = + override def nonEmpty[A](fa: F[A]): Boolean = !isEmpty(fa) /** @@ -581,6 +573,11 @@ import Foldable.sentinel val F = self val G = Foldable[G] } + + override def unorderedFold[A: CommutativeMonoid](fa: F[A]): A = fold(fa) + + override def unorderedFoldMap[A, B: CommutativeMonoid](fa: F[A])(f: (A) => B): B = + foldMap(fa)(f) } object Foldable { diff --git a/core/src/main/scala/cats/Traverse.scala b/core/src/main/scala/cats/Traverse.scala index cbfbae2712..3e1f1ac5fb 100644 --- a/core/src/main/scala/cats/Traverse.scala +++ b/core/src/main/scala/cats/Traverse.scala @@ -16,7 +16,7 @@ import simulacrum.typeclass * * See: [[https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf The Essence of the Iterator Pattern]] */ -@typeclass trait Traverse[F[_]] extends Functor[F] with Foldable[F] { self => +@typeclass trait Traverse[F[_]] extends Functor[F] with Foldable[F] with UnorderedTraverse[F] { self => /** * Given a function which returns a G effect, thread this effect @@ -124,4 +124,10 @@ import simulacrum.typeclass */ def zipWithIndex[A](fa: F[A]): F[(A, Int)] = mapWithIndex(fa)((a, i) => (a, i)) + + override def unorderedTraverse[G[_] : CommutativeApplicative, A, B](sa: F[A])(f: (A) => G[B]): G[F[B]] = + traverse(sa)(f) + + override def unorderedSequence[G[_] : CommutativeApplicative, A](fga: F[G[A]]): G[F[A]] = + sequence(fga) } diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala new file mode 100644 index 0000000000..b0d7270f84 --- /dev/null +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -0,0 +1,71 @@ +package cats + +import cats.kernel.CommutativeMonoid +import simulacrum.typeclass +import cats.instances.long._ +/** + * `UnorderedFoldable` is like a `Foldable` for unordered containers. + */ +@typeclass trait UnorderedFoldable[F[_]] { + + def unorderedFoldMap[A, B: CommutativeMonoid](fa: F[A])(f: A => B): B + + def unorderedFold[A: CommutativeMonoid](fa: F[A]): A = + unorderedFoldMap(fa)(identity) + + + /** + * Returns true if there are no elements. Otherwise false. + */ + def isEmpty[A](fa: F[A]): Boolean = + exists(fa)(Function.const(true)) + + def nonEmpty[A](fa: F[A]): Boolean = + !isEmpty(fa) + + /** + * Check whether at least one element satisfies the predicate. + * + * If there are no elements, the result is `false`. + */ + def exists[A](fa: F[A])(p: A => Boolean): Boolean = + unorderedFoldMap(fa)(a => Eval.later(p(a)))(UnorderedFoldable.commutativeMonoidEval(UnorderedFoldable.orMonoid)) + .value + + /** + * Check whether all elements satisfy the predicate. + * + * If there are no elements, the result is `true`. + */ + def forall[A](fa: F[A])(p: A => Boolean): Boolean = + unorderedFoldMap(fa)(a => Eval.later(p(a)))(UnorderedFoldable.commutativeMonoidEval(UnorderedFoldable.andMonoid)) + .value + + /** + * The size of this UnorderedFoldable. + * + * This is overriden in structures that have more efficient size implementations + * (e.g. Vector, Set, Map). + * + * Note: will not terminate for infinite-sized collections. + */ + def size[A](fa: F[A]): Long = unorderedFoldMap(fa)(_ => 1L) +} + +object UnorderedFoldable { + private val orMonoid: CommutativeMonoid[Boolean] = new CommutativeMonoid[Boolean] { + val empty: Boolean = false + + def combine(x: Boolean, y: Boolean): Boolean = x || y + } + + private val andMonoid: CommutativeMonoid[Boolean] = new CommutativeMonoid[Boolean] { + val empty: Boolean = true + + def combine(x: Boolean, y: Boolean): Boolean = x && y + } + + private def commutativeMonoidEval[A: CommutativeMonoid]: CommutativeMonoid[Eval[A]] = + new EvalMonoid[A] with CommutativeMonoid[Eval[A]] { val algebra = Monoid[A] } + +} diff --git a/core/src/main/scala/cats/UnorderedTraverse.scala b/core/src/main/scala/cats/UnorderedTraverse.scala new file mode 100644 index 0000000000..3fab6f1124 --- /dev/null +++ b/core/src/main/scala/cats/UnorderedTraverse.scala @@ -0,0 +1,13 @@ +package cats + +import simulacrum.typeclass + +/** + * `UnorderedTraverse` is like a `Traverse` for unordered containers. + */ +@typeclass trait UnorderedTraverse[F[_]] extends UnorderedFoldable[F] { + def unorderedTraverse[G[_]: CommutativeApplicative, A, B](sa: F[A])(f: A => G[B]): G[F[B]] + + def unorderedSequence[G[_]: CommutativeApplicative, A](fga: F[G[A]]): G[F[A]] = + unorderedTraverse(fga)(identity) +} diff --git a/core/src/main/scala/cats/instances/map.scala b/core/src/main/scala/cats/instances/map.scala index 8041c36b92..573ce85556 100644 --- a/core/src/main/scala/cats/instances/map.scala +++ b/core/src/main/scala/cats/instances/map.scala @@ -1,6 +1,8 @@ package cats package instances +import cats.kernel.CommutativeMonoid + import scala.annotation.tailrec trait MapInstances extends cats.kernel.instances.MapInstances { @@ -14,8 +16,16 @@ trait MapInstances extends cats.kernel.instances.MapInstances { } // scalastyle:off method.length - implicit def catsStdInstancesForMap[K]: FlatMap[Map[K, ?]] = - new FlatMap[Map[K, ?]] { + implicit def catsStdInstancesForMap[K]: UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] = + new UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] { + + def unorderedTraverse[G[_], A, B](fa: Map[K, A])(f: A => G[B])(implicit G: CommutativeApplicative[G]): G[Map[K, B]] = { + val gba: Eval[G[Map[K, B]]] = Always(G.pure(Map.empty)) + val gbb = Foldable.iterateRight(fa, gba){ (kv, lbuf) => + G.map2Eval(f(kv._2), lbuf)({ (b, buf) => buf + (kv._1 -> b)}) + }.value + G.map(gbb)(_.toMap) + } override def map[A, B](fa: Map[K, A])(f: A => B): Map[K, B] = fa.map { case (k, a) => (k, f(a)) } @@ -39,6 +49,9 @@ trait MapInstances extends cats.kernel.instances.MapInstances { def flatMap[A, B](fa: Map[K, A])(f: (A) => Map[K, B]): Map[K, B] = fa.flatMap { case (k, a) => f(a).get(k).map((k, _)) } + def unorderedFoldMap[A, B: CommutativeMonoid](fa: Map[K, A])(f: (A) => B) = + fa.foldLeft(Monoid[B].empty){ case (b, (k, a)) => Monoid[B].combine(b, f(a)) } + def tailRecM[A, B](a: A)(f: A => Map[K, Either[A, B]]): Map[K, B] = { val bldr = Map.newBuilder[K, B] @@ -57,6 +70,13 @@ trait MapInstances extends cats.kernel.instances.MapInstances { f(a).foreach { case (k, a) => descend(k, a) } bldr.result } + + + override def isEmpty[A](fa: Map[K, A]): Boolean = fa.isEmpty + + override def unorderedFold[A](fa: Map[K, A])(implicit A: CommutativeMonoid[A]): A = + A.combineAll(fa.values) + } // scalastyle:on method.length } diff --git a/core/src/main/scala/cats/instances/set.scala b/core/src/main/scala/cats/instances/set.scala index 21a6dfd48b..0343f5ae97 100644 --- a/core/src/main/scala/cats/instances/set.scala +++ b/core/src/main/scala/cats/instances/set.scala @@ -1,17 +1,38 @@ package cats package instances +import cats.kernel.CommutativeMonoid + import cats.syntax.show._ trait SetInstances extends cats.kernel.instances.SetInstances { - implicit val catsStdInstancesForSet: MonoidK[Set] = - new MonoidK[Set] { + implicit val catsStdInstancesForSet: UnorderedTraverse[Set] with MonoidK[Set] = + new UnorderedTraverse[Set] with MonoidK[Set] { + + def unorderedTraverse[G[_]: CommutativeApplicative, A, B](sa: Set[A])(f: A => G[B]): G[Set[B]] = + sa.foldLeft(Applicative[G].pure(Set.empty[B])) { (acc, a) => + Apply[G].map2(acc, f(a))(_ + _) + } + + override def unorderedSequence[G[_]: CommutativeApplicative, A](sa: Set[G[A]]): G[Set[A]] = + sa.foldLeft(Applicative[G].pure(Set.empty[A])) { (acc, a) => + Apply[G].map2(acc, a)(_ + _) + } def empty[A]: Set[A] = Set.empty[A] def combineK[A](x: Set[A], y: Set[A]): Set[A] = x | y + def unorderedFoldMap[A, B](fa: Set[A])(f: A => B)(implicit B: CommutativeMonoid[B]): B = + fa.foldLeft(B.empty)((b, a) => B.combine(f(a), b)) + + override def unorderedFold[A](fa: Set[A])(implicit A: CommutativeMonoid[A]): A = A.combineAll(fa) + + override def forall[A](fa: Set[A])(p: A => Boolean): Boolean = + fa.forall(p) + + override def isEmpty[A](fa: Set[A]): Boolean = fa.isEmpty } implicit def catsStdShowForSet[A:Show]: Show[Set[A]] = new Show[Set[A]] { diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index eb6bb70460..66201f5184 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -1,7 +1,7 @@ package cats package syntax -trait FoldableSyntax extends Foldable.ToFoldableOps { +trait FoldableSyntax extends Foldable.ToFoldableOps with UnorderedFoldable.ToUnorderedFoldableOps { implicit final def catsSyntaxNestedFoldable[F[_]: Foldable, G[_], A](fga: F[G[A]]): NestedFoldableOps[F, G, A] = new NestedFoldableOps[F, G, A](fga) diff --git a/docs/src/main/tut/typeclasses/foldable.md b/docs/src/main/tut/typeclasses/foldable.md index 7c2ec546ba..9fcbbd310a 100644 --- a/docs/src/main/tut/typeclasses/foldable.md +++ b/docs/src/main/tut/typeclasses/foldable.md @@ -10,7 +10,7 @@ scaladoc: "#cats.Foldable" Foldable type class instances can be defined for data structures that can be folded to a summary value. -In the case of a collection (such as `List` or `Set`), these methods will fold +In the case of a collection (such as `List` or `Vector`), these methods will fold together (combine) the values contained in the collection to produce a single result. Most collection types have `foldLeft` methods, which will usually be used by the associated `Foldable[_]` instance. diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 0de46d551f..2d7d261956 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -5,7 +5,7 @@ import cats.implicits._ import scala.collection.mutable -trait FoldableLaws[F[_]] { +trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[F] { implicit def F: Foldable[F] def leftFoldConsistentWithFoldMap[A, B]( @@ -26,56 +26,10 @@ trait FoldableLaws[F[_]] { fa.foldMap(f) <-> fa.foldRight(Later(M.empty))((a, lb) => lb.map(f(a) |+| _)).value } - def existsConsistentWithFind[A]( - fa: F[A], - p: A => Boolean - ): Boolean = { + def existsConsistentWithFind[A](fa: F[A], p: A => Boolean): Boolean = { F.exists(fa)(p) == F.find(fa)(p).isDefined } - def existsLazy[A](fa: F[A]): Boolean = { - var i = 0 - F.exists(fa){ _ => - i = i + 1 - true - } - i == (if (F.isEmpty(fa)) 0 else 1) - } - - def forallLazy[A](fa: F[A]): Boolean = { - var i = 0 - F.forall(fa){ _ => - i = i + 1 - false - } - i == (if (F.isEmpty(fa)) 0 else 1) - } - - def forallConsistentWithExists[A]( - fa: F[A], - p: A => Boolean - ): Boolean = { - if (F.forall(fa)(p)) { - val negationExists = F.exists(fa)(a => !(p(a))) - - // if p is true for all elements, then there cannot be an element for which - // it does not hold. - !negationExists && - // if p is true for all elements, then either there must be no elements - // or there must exist an element for which it is true. - (F.isEmpty(fa) || F.exists(fa)(p)) - } else true // can't test much in this case - } - - /** - * If `F[A]` is empty, forall must return true. - */ - def forallEmpty[A]( - fa: F[A], - p: A => Boolean - ): Boolean = { - !F.isEmpty(fa) || F.forall(fa)(p) - } /** * Monadic folding with identity monad is analogous to `foldLeft`. diff --git a/laws/src/main/scala/cats/laws/TraverseLaws.scala b/laws/src/main/scala/cats/laws/TraverseLaws.scala index aa6b74a547..e796b9070c 100644 --- a/laws/src/main/scala/cats/laws/TraverseLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseLaws.scala @@ -6,7 +6,7 @@ import cats.data.{Const, Nested, State, StateT} import cats.syntax.traverse._ import cats.syntax.foldable._ -trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] { +trait TraverseLaws[F[_]] extends FunctorLaws[F] with FoldableLaws[F] with UnorderedTraverseLaws[F] { implicit override def F: Traverse[F] def traverseIdentity[A, B](fa: F[A], f: A => B): IsEq[F[B]] = { diff --git a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala new file mode 100644 index 0000000000..ee98a97708 --- /dev/null +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -0,0 +1,61 @@ +package cats +package laws + +import cats.kernel.CommutativeMonoid + +trait UnorderedFoldableLaws[F[_]] { + implicit def F: UnorderedFoldable[F] + + def unorderedFoldConsistentWithUnorderedFoldMap[A: CommutativeMonoid](fa: F[A]): IsEq[A] = + F.unorderedFoldMap(fa)(identity) <-> F.unorderedFold(fa) + + + + def forallConsistentWithExists[A](fa: F[A], p: A => Boolean): Boolean = { + if (F.forall(fa)(p)) { + val negationExists = F.exists(fa)(a => !(p(a))) + + // if p is true for all elements, then there cannot be an element for which + // it does not hold. + !negationExists && + // if p is true for all elements, then either there must be no elements + // or there must exist an element for which it is true. + (F.isEmpty(fa) || F.exists(fa)(p)) + } else true // can't test much in this case + } + + def existsLazy[A](fa: F[A]): Boolean = { + var i = 0 + F.exists(fa){ _ => + i = i + 1 + true + } + i == (if (F.isEmpty(fa)) 0 else 1) + } + + def forallLazy[A](fa: F[A]): Boolean = { + var i = 0 + F.forall(fa){ _ => + i = i + 1 + false + } + i == (if (F.isEmpty(fa)) 0 else 1) + } + + /** + * If `F[A]` is empty, forall must return true. + */ + def forallEmpty[A](fa: F[A], p: A => Boolean): Boolean = { + !F.isEmpty(fa) || F.forall(fa)(p) + } + + + def nonEmptyRef[A](fa: F[A]): IsEq[Boolean] = + F.nonEmpty(fa) <-> !F.isEmpty(fa) + +} + +object UnorderedFoldableLaws { + def apply[F[_]](implicit ev: UnorderedFoldable[F]): UnorderedFoldableLaws[F] = + new UnorderedFoldableLaws[F] { def F: UnorderedFoldable[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/UnorderedTraverseLaws.scala b/laws/src/main/scala/cats/laws/UnorderedTraverseLaws.scala new file mode 100644 index 0000000000..0841f4255b --- /dev/null +++ b/laws/src/main/scala/cats/laws/UnorderedTraverseLaws.scala @@ -0,0 +1,62 @@ +package cats +package laws + +import cats.data.Nested + +trait UnorderedTraverseLaws[F[_]] extends UnorderedFoldableLaws[F] { + implicit def F: UnorderedTraverse[F] + + def unorderedTraverseIdentity[A, B](fa: F[A])(f: A => B)(implicit ev: Functor[F]): IsEq[F[B]] = + F.unorderedTraverse[Id, A, B](fa)(f) <-> (ev.map(fa)(f)) + + def unorderedTraverseSequentialComposition[A, B, C, M[_], N[_]] + (fa: F[A], + f: A => M[B], + g: B => N[C]) + (implicit N: CommutativeApplicative[N], + M: CommutativeApplicative[M]): IsEq[Nested[M, N, F[C]]] = { + + val lhs = Nested(M.map(F.unorderedTraverse(fa)(f))(fb => F.unorderedTraverse(fb)(g))) + val rhs = F.unorderedTraverse[Nested[M, N, ?], A, C](fa)(a => Nested(M.map(f(a))(g))) + lhs <-> rhs + } + + def unorderedTraverseParallelComposition[A, B, M[_], N[_]] + (fa: F[A], + f: A => M[B], + g: A => N[B]) + (implicit N: CommutativeApplicative[N], + M: CommutativeApplicative[M]): IsEq[(M[F[B]], N[F[B]])] = { + + type MN[Z] = (M[Z], N[Z]) + implicit val MN = new CommutativeApplicative[MN] { + def pure[X](x: X): MN[X] = (M.pure(x), N.pure(x)) + def ap[X, Y](f: MN[X => Y])(fa: MN[X]): MN[Y] = { + val (fam, fan) = fa + val (fm, fn) = f + (M.ap(fm)(fam), N.ap(fn)(fan)) + } + override def map[X, Y](fx: MN[X])(f: X => Y): MN[Y] = { + val (mx, nx) = fx + (M.map(mx)(f), N.map(nx)(f)) + } + override def product[X, Y](fx: MN[X], fy: MN[Y]): MN[(X, Y)] = { + val (mx, nx) = fx + val (my, ny) = fy + (M.product(mx, my), N.product(nx, ny)) + } + } + val lhs: MN[F[B]] = F.unorderedTraverse[MN, A, B](fa)(a => (f(a), g(a))) + val rhs: MN[F[B]] = (F.unorderedTraverse(fa)(f), F.unorderedTraverse(fa)(g)) + lhs <-> rhs + } + + def unorderedSequenceConsistent[A, G[_]: CommutativeApplicative](fga: F[G[A]]): IsEq[G[F[A]]] = + F.unorderedTraverse(fga)(identity) <-> F.unorderedSequence(fga) + +} + +object UnorderedTraverseLaws { + def apply[F[_]](implicit ev: UnorderedTraverse[F]): UnorderedTraverseLaws[F] = + new UnorderedTraverseLaws[F] { def F: UnorderedTraverse[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index 4c0c1a5ee0..ebc68c82d5 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -2,20 +2,19 @@ package cats package laws package discipline +import cats.kernel.CommutativeMonoid import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Prop._ -import org.typelevel.discipline.Laws - import cats.instances.list._ import arbitrary.catsLawsArbitraryForPartialFunction -trait FoldableTests[F[_]] extends Laws { +trait FoldableTests[F[_]] extends UnorderedFoldableTests[F] { def laws: FoldableLaws[F] def foldable[A: Arbitrary, B: Arbitrary](implicit ArbFA: Arbitrary[F[A]], - A: Monoid[A], - B: Monoid[B], + A: CommutativeMonoid[A], + B: CommutativeMonoid[B], CogenA: Cogen[A], CogenB: Cogen[B], EqA: Eq[A], @@ -26,13 +25,11 @@ trait FoldableTests[F[_]] extends Laws { ): RuleSet = { new DefaultRuleSet( name = "foldable", - parent = None, + parent = Some(unorderedFoldable[A, B]), "foldLeft consistent with foldMap" -> forAll(laws.leftFoldConsistentWithFoldMap[A, B] _), "foldRight consistent with foldMap" -> forAll(laws.rightFoldConsistentWithFoldMap[A, B] _), "ordered constistency" -> forAll(laws.orderedConsistency[A] _), "exists consistent with find" -> forAll(laws.existsConsistentWithFind[A] _), - "forall consistent with exists" -> forAll(laws.forallConsistentWithExists[A] _), - "forall true if empty" -> forAll(laws.forallEmpty[A] _), "exists is lazy" -> forAll(laws.existsLazy[A] _), "forall is lazy" -> forAll(laws.forallLazy[A] _), "foldM identity" -> forAll(laws.foldMIdentity[A, B] _), diff --git a/laws/src/main/scala/cats/laws/discipline/NonEmptyTraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/NonEmptyTraverseTests.scala index afe6182ff3..86e2438b6c 100644 --- a/laws/src/main/scala/cats/laws/discipline/NonEmptyTraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/NonEmptyTraverseTests.scala @@ -3,28 +3,36 @@ package cats.laws.discipline import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop.forAll -import cats.{Applicative, Eq, Monoid, NonEmptyTraverse} +import cats.kernel.CommutativeMonoid +import cats.{Applicative, CommutativeApplicative, Eq, NonEmptyTraverse} import cats.laws.NonEmptyTraverseLaws trait NonEmptyTraverseTests[F[_]] extends TraverseTests[F] with ReducibleTests[F] { def laws: NonEmptyTraverseLaws[F] - def nonEmptyTraverse[G[_]: Applicative, A: Arbitrary, B: Arbitrary, C: Arbitrary, M: Arbitrary, X[_]: Applicative, Y[_]: Applicative](implicit + def nonEmptyTraverse[G[_]: Applicative, A: Arbitrary, B: Arbitrary, C: Arbitrary, M: Arbitrary, X[_], Y[_]](implicit ArbFA: Arbitrary[F[A]], ArbXB: Arbitrary[X[B]], ArbYB: Arbitrary[Y[B]], ArbYC: Arbitrary[Y[C]], ArbFB: Arbitrary[F[B]], + ArbFM: Arbitrary[F[M]], + ArbXM: Arbitrary[X[M]], + ArbYM: Arbitrary[Y[M]], ArbFGA: Arbitrary[F[G[A]]], + ArbFXM: Arbitrary[F[X[M]]], ArbGB: Arbitrary[G[B]], + ArbGM: Arbitrary[G[M]], CogenA: Cogen[A], CogenB: Cogen[B], CogenC: Cogen[C], CogenM: Cogen[M], - M: Monoid[M], - MA: Monoid[A], - MB: Monoid[B], + M: CommutativeMonoid[M], + MA: CommutativeMonoid[A], + MB: CommutativeMonoid[B], + CX: CommutativeApplicative[X], + CY: CommutativeApplicative[Y], EqFA: Eq[F[A]], EqFC: Eq[F[C]], EqG: Eq[G[Unit]], @@ -33,7 +41,9 @@ trait NonEmptyTraverseTests[F[_]] extends TraverseTests[F] with ReducibleTests[F EqB: Eq[B], EqXYFC: Eq[X[Y[F[C]]]], EqXFB: Eq[X[F[B]]], + EqXFM: Eq[X[F[M]]], EqYFB: Eq[Y[F[B]]], + EqYFM: Eq[Y[F[M]]], EqOptionA: Eq[Option[A]] ): RuleSet = { implicit def EqXFBYFB : Eq[(X[F[B]], Y[F[B]])] = new Eq[(X[F[B]], Y[F[B]])] { diff --git a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala index 298f8fcf2f..5a61a88601 100644 --- a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala @@ -4,7 +4,7 @@ package discipline import cats.instances.option._ import cats.instances.long._ - +import cats.kernel.CommutativeMonoid import org.scalacheck.{Arbitrary, Cogen} import org.scalacheck.Prop.forAll @@ -23,8 +23,8 @@ trait ReducibleTests[F[_]] extends FoldableTests[F] { EqB: Eq[B], EqFA: Eq[F[A]], EqOptionA: Eq[Option[A]], - MonoidA: Monoid[A], - MonoidB: Monoid[B] + MonoidA: CommutativeMonoid[A], + MonoidB: CommutativeMonoid[B] ): RuleSet = new DefaultRuleSet( name = "reducible", diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala index 0184105349..6171e64a03 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -3,25 +3,29 @@ package laws package discipline import cats.instances.option._ - +import cats.kernel.CommutativeMonoid import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ -trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { +trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] with UnorderedTraverseTests[F] { def laws: TraverseLaws[F] - def traverse[A: Arbitrary, B: Arbitrary, C: Arbitrary, M: Arbitrary, X[_]: Applicative, Y[_]: Applicative](implicit + def traverse[A: Arbitrary, B: Arbitrary, C: Arbitrary, M: Arbitrary, X[_]: CommutativeApplicative, Y[_]: CommutativeApplicative](implicit ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], ArbXB: Arbitrary[X[B]], + ArbXM: Arbitrary[X[M]], ArbYB: Arbitrary[Y[B]], ArbYC: Arbitrary[Y[C]], + ArbYM: Arbitrary[Y[M]], + ArbFXM: Arbitrary[F[X[M]]], CogenA: Cogen[A], CogenB: Cogen[B], CogenC: Cogen[C], CogenM: Cogen[M], - M: Monoid[M], - MA: Monoid[A], + M: CommutativeMonoid[M], + MA: CommutativeMonoid[A], EqFA: Eq[F[A]], EqFC: Eq[F[C]], EqM: Eq[M], @@ -29,6 +33,8 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { EqXYFC: Eq[X[Y[F[C]]]], EqXFB: Eq[X[F[B]]], EqYFB: Eq[Y[F[B]]], + EqXFM: Eq[X[F[M]]], + EqYFM: Eq[Y[F[M]]], EqOptionA: Eq[Option[A]] ): RuleSet = { implicit def EqXFBYFB : Eq[(X[F[B]], Y[F[B]])] = new Eq[(X[F[B]], Y[F[B]])] { @@ -38,7 +44,7 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { new RuleSet { def name: String = "traverse" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(functor[A, B, C], foldable[A, M]) + def parents: Seq[RuleSet] = Seq(functor[A, B, C], foldable[A, M], unorderedTraverse[A, M, C, X, Y]) def props: Seq[(String, Prop)] = Seq( "traverse identity" -> forAll(laws.traverseIdentity[A, C] _), "traverse sequential composition" -> forAll(laws.traverseSequentialComposition[A, B, C, X, Y] _), diff --git a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala new file mode 100644 index 0000000000..bdbd5c8a88 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala @@ -0,0 +1,37 @@ +package cats +package laws +package discipline + +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ +import org.typelevel.discipline.Laws +import cats.kernel.CommutativeMonoid +import cats.instances.boolean._ + +trait UnorderedFoldableTests[F[_]] extends Laws { + def laws: UnorderedFoldableLaws[F] + + + def unorderedFoldable[A: Arbitrary, B: Arbitrary](implicit + ArbFA: Arbitrary[F[A]], + ArbF: Arbitrary[A => B], + CogenA: Cogen[A], + A: CommutativeMonoid[A], + B: CommutativeMonoid[B], + EqFA: Eq[A], + EqFB: Eq[B] + ): RuleSet = + new DefaultRuleSet( + name = "unorderedFoldable", + parent = None, + "unorderedFold consistent with unorderedFoldMap" -> forAll(laws.unorderedFoldConsistentWithUnorderedFoldMap[A] _), + "forall consistent with exists" -> forAll(laws.forallConsistentWithExists[A] _), + "forall true if empty" -> forAll(laws.forallEmpty[A] _), + "nonEmpty reference" -> forAll(laws.nonEmptyRef[A] _) + ) +} + +object UnorderedFoldableTests { + def apply[F[_]: UnorderedFoldable]: UnorderedFoldableTests[F] = + new UnorderedFoldableTests[F] { def laws: UnorderedFoldableLaws[F] = UnorderedFoldableLaws[F] } +} diff --git a/laws/src/main/scala/cats/laws/discipline/UnorderedTraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedTraverseTests.scala new file mode 100644 index 0000000000..161354fabf --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedTraverseTests.scala @@ -0,0 +1,46 @@ +package cats +package laws +package discipline + +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ +import cats.kernel.CommutativeMonoid + +trait UnorderedTraverseTests[F[_]] extends UnorderedFoldableTests[F] { + def laws: UnorderedTraverseLaws[F] + + + def unorderedTraverse[A: Arbitrary, B: Arbitrary, C: Arbitrary, X[_]: CommutativeApplicative, Y[_]: CommutativeApplicative] + (implicit ArbFA: Arbitrary[F[A]], + ArbFXB: Arbitrary[F[X[B]]], + ArbXB: Arbitrary[X[B]], + ArbYB: Arbitrary[Y[B]], + ArbYC: Arbitrary[Y[C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + Ca: CommutativeMonoid[A], + Cb: CommutativeMonoid[B], + EqA: Eq[A], + EqB: Eq[B], + EqXYFC: Eq[X[Y[F[C]]]], + EqXFB: Eq[X[F[B]]], + EqYFB: Eq[Y[F[B]]] + ): RuleSet = { + implicit def EqXFBYFB : Eq[(X[F[B]], Y[F[B]])] = new Eq[(X[F[B]], Y[F[B]])] { + override def eqv(x: (X[F[B]], Y[F[B]]), y: (X[F[B]], Y[F[B]])): Boolean = + EqXFB.eqv(x._1, y._1) && EqYFB.eqv(x._2, y._2) + } + new DefaultRuleSet( + name = "unorderedTraverse", + parent = Some(unorderedFoldable[A, B]), + "unordered traverse sequential composition" -> forAll(laws.unorderedTraverseSequentialComposition[A, B, C, X, Y] _), + "unordered traverse parallel composition" -> forAll(laws.unorderedTraverseParallelComposition[A, B, X, Y] _), + "unordered traverse consistent with sequence" -> forAll(laws.unorderedSequenceConsistent[B, X] _) + ) + } +} + +object UnorderedTraverseTests { + def apply[F[_]: UnorderedTraverse]: UnorderedTraverseTests[F] = + new UnorderedTraverseTests[F] { def laws: UnorderedTraverseLaws[F] = UnorderedTraverseLaws[F] } +} diff --git a/tests/src/test/scala/cats/tests/ListSuite.scala b/tests/src/test/scala/cats/tests/ListSuite.scala index 30ce059048..595b9f006f 100644 --- a/tests/src/test/scala/cats/tests/ListSuite.scala +++ b/tests/src/test/scala/cats/tests/ListSuite.scala @@ -16,7 +16,7 @@ class ListSuite extends CatsSuite { checkAll("List[Int]", AlternativeTests[List].alternative[Int, Int, Int]) checkAll("Alternative[List]", SerializableTests.serializable(Alternative[List])) - checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, List[Int], Option, Option]) + checkAll("List[Int] with Option", TraverseTests[List].traverse[Int, Int, Int, Set[Int], Option, Option]) checkAll("Traverse[List]", SerializableTests.serializable(Traverse[List])) checkAll("ZipList[Int]", CommutativeApplyTests[ZipList].commutativeApply[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/MapSuite.scala b/tests/src/test/scala/cats/tests/MapSuite.scala index 6d0f49e145..9be630d7b5 100644 --- a/tests/src/test/scala/cats/tests/MapSuite.scala +++ b/tests/src/test/scala/cats/tests/MapSuite.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.discipline.{FlatMapTests, SerializableTests, SemigroupalTests} +import cats.laws.discipline.{FlatMapTests, SemigroupalTests, SerializableTests, UnorderedTraverseTests} class MapSuite extends CatsSuite { implicit val iso = SemigroupalTests.Isomorphisms.invariant[Map[Int, ?]] @@ -12,6 +12,10 @@ class MapSuite extends CatsSuite { checkAll("Map[Int, Int]", FlatMapTests[Map[Int, ?]].flatMap[Int, Int, Int]) checkAll("FlatMap[Map[Int, ?]]", SerializableTests.serializable(FlatMap[Map[Int, ?]])) + checkAll("Map[Int, Int] with Option", UnorderedTraverseTests[Map[Int, ?]].unorderedTraverse[Int, Int, Int, Option, Option]) + checkAll("UnorderedTraverse[Map[Int, ?]]", SerializableTests.serializable(UnorderedTraverse[Map[Int, ?]])) + + test("show isn't empty and is formatted as expected") { forAll { (map: Map[Int, String]) => map.show.nonEmpty should === (true) diff --git a/tests/src/test/scala/cats/tests/NestedSuite.scala b/tests/src/test/scala/cats/tests/NestedSuite.scala index 8ad49c23f7..8bb3faa3ae 100644 --- a/tests/src/test/scala/cats/tests/NestedSuite.scala +++ b/tests/src/test/scala/cats/tests/NestedSuite.scala @@ -115,7 +115,7 @@ class NestedSuite extends CatsSuite { { // Traverse composition implicit val instance = ListWrapper.traverse - checkAll("Nested[List, ListWrapper, ?]", TraverseTests[Nested[List, ListWrapper, ?]].traverse[Int, Int, Int, List[Int], Option, Option]) + checkAll("Nested[List, ListWrapper, ?]", TraverseTests[Nested[List, ListWrapper, ?]].traverse[Int, Int, Int, Set[Int], Option, Option]) checkAll("Traverse[Nested[List, ListWrapper, ?]]", SerializableTests.serializable(Traverse[Nested[List, ListWrapper, ?]])) } diff --git a/tests/src/test/scala/cats/tests/ParallelSuite.scala b/tests/src/test/scala/cats/tests/ParallelSuite.scala index dc4117a2d4..2df5cc5dcb 100644 --- a/tests/src/test/scala/cats/tests/ParallelSuite.scala +++ b/tests/src/test/scala/cats/tests/ParallelSuite.scala @@ -1,4 +1,6 @@ -package cats.tests + +package cats +package tests import cats._ import cats.data.NonEmptyList.ZipNonEmptyList @@ -170,6 +172,7 @@ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest { 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, ?]])) { diff --git a/tests/src/test/scala/cats/tests/QueueSuite.scala b/tests/src/test/scala/cats/tests/QueueSuite.scala index 621724855b..aa75a474d2 100644 --- a/tests/src/test/scala/cats/tests/QueueSuite.scala +++ b/tests/src/test/scala/cats/tests/QueueSuite.scala @@ -18,7 +18,7 @@ class QueueSuite extends CatsSuite { checkAll("Queue[Int]", MonadTests[Queue].monad[Int, Int, Int]) checkAll("Monad[Queue]", SerializableTests.serializable(Monad[Queue])) - checkAll("Queue[Int] with Option", TraverseTests[Queue].traverse[Int, Int, Int, List[Int], Option, Option]) + checkAll("Queue[Int] with Option", TraverseTests[Queue].traverse[Int, Int, Int, Set[Int], Option, Option]) checkAll("Traverse[Queue]", SerializableTests.serializable(Traverse[Queue])) test("show") { diff --git a/tests/src/test/scala/cats/tests/SetSuite.scala b/tests/src/test/scala/cats/tests/SetSuite.scala index db9cbe319d..a83764f2ed 100644 --- a/tests/src/test/scala/cats/tests/SetSuite.scala +++ b/tests/src/test/scala/cats/tests/SetSuite.scala @@ -1,14 +1,20 @@ package cats package tests -import cats.laws.discipline.{MonoidKTests, SerializableTests} import cats.kernel.laws.discipline.MonoidTests +import cats.data.Validated +import cats.laws.discipline.{MonoidKTests, SerializableTests, UnorderedTraverseTests} +import cats.laws.discipline.arbitrary._ + class SetSuite extends CatsSuite { checkAll("Set[Int]", MonoidTests[Set[Int]].monoid) checkAll("Set[Int]", MonoidKTests[Set].monoidK[Int]) checkAll("MonoidK[Set]", SerializableTests.serializable(MonoidK[Set])) + checkAll("Set[Int]", UnorderedTraverseTests[Set].unorderedTraverse[Int, Int, Int, Validated[Int, ?], Option]) + checkAll("UnorderedTraverse[Set]", SerializableTests.serializable(UnorderedTraverse[Set])) + test("show"){ Set(1, 1, 2, 3).show should === ("Set(1, 2, 3)") Set.empty[String].show should === ("Set()") diff --git a/tests/src/test/scala/cats/tests/StreamSuite.scala b/tests/src/test/scala/cats/tests/StreamSuite.scala index 84d65f71cf..bfc4cb51ef 100644 --- a/tests/src/test/scala/cats/tests/StreamSuite.scala +++ b/tests/src/test/scala/cats/tests/StreamSuite.scala @@ -19,7 +19,7 @@ class StreamSuite extends CatsSuite { checkAll("Stream[Int]", MonadTests[Stream].monad[Int, Int, Int]) checkAll("Monad[Stream]", SerializableTests.serializable(Monad[Stream])) - checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, List[Int], Option, Option]) + checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, Set[Int], Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) // Can't test applicative laws as they don't terminate diff --git a/tests/src/test/scala/cats/tests/VectorSuite.scala b/tests/src/test/scala/cats/tests/VectorSuite.scala index c8b2a89804..0ff1b03562 100644 --- a/tests/src/test/scala/cats/tests/VectorSuite.scala +++ b/tests/src/test/scala/cats/tests/VectorSuite.scala @@ -15,7 +15,7 @@ class VectorSuite extends CatsSuite { checkAll("Vector[Int]", AlternativeTests[Vector].alternative[Int, Int, Int]) checkAll("Alternative[Vector]", SerializableTests.serializable(Alternative[Vector])) - checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, List[Int], Option, Option]) + checkAll("Vector[Int] with Option", TraverseTests[Vector].traverse[Int, Int, Int, Set[Int], Option, Option]) checkAll("Traverse[Vector]", SerializableTests.serializable(Traverse[Vector])) checkAll("ZipVector[Int]", CommutativeApplyTests[ZipVector].commutativeApply[Int, Int, Int])