From d308cf7ef105ee95110ffb74f4fbb77f40da9c1f Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 18 Oct 2017 18:49:17 -0400 Subject: [PATCH] Add UnorderedFoldable and UnorderedTraverse and move traversal functions there --- .../scala/cats/CommutativeApplicative.scala | 54 +-------------- .../main/scala/cats/UnorderedFoldable.scala | 66 +++++++++++++++++++ .../main/scala/cats/UnorderedTraverse.scala | 14 ++++ core/src/main/scala/cats/instances/map.scala | 21 ++---- core/src/main/scala/cats/instances/set.scala | 41 +++++------- .../cats/laws/UnorderedFoldableLaws.scala | 63 ++++++++++++++++++ .../cats/laws/UnorderedTraverseLaws.scala | 62 +++++++++++++++++ .../discipline/UnorderedFoldableTests.scala | 39 +++++++++++ .../discipline/UnorderedTraverseTests.scala | 46 +++++++++++++ .../test/scala/cats/tests/FoldableTests.scala | 16 ----- .../src/test/scala/cats/tests/MapTests.scala | 28 +------- .../scala/cats/tests/RegressionTests.scala | 5 -- .../src/test/scala/cats/tests/SetTests.scala | 41 ++---------- 13 files changed, 324 insertions(+), 172 deletions(-) create mode 100644 core/src/main/scala/cats/UnorderedFoldable.scala create mode 100644 core/src/main/scala/cats/UnorderedTraverse.scala create mode 100644 laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala create mode 100644 laws/src/main/scala/cats/laws/UnorderedTraverseLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/UnorderedTraverseTests.scala diff --git a/core/src/main/scala/cats/CommutativeApplicative.scala b/core/src/main/scala/cats/CommutativeApplicative.scala index 2bd13b5cd8..99ca3fcdfc 100644 --- a/core/src/main/scala/cats/CommutativeApplicative.scala +++ b/core/src/main/scala/cats/CommutativeApplicative.scala @@ -11,56 +11,4 @@ import simulacrum.typeclass * * Must obey the laws defined in cats.laws.CommutativeApplicativeLaws. */ -@typeclass trait CommutativeApplicative[F[_]] extends Applicative[F] with CommutativeApply[F] { - def traverseUnordered[A, B](sa: Set[A])(f: A => F[B]): F[Set[B]] = - sa.foldLeft(pure(Set.empty[B])) { (acc, a) => - map2(acc, f(a))(_ + _) - } - - def sequenceUnordered[A](sa: Set[F[A]]): F[Set[A]] = - sa.foldLeft(pure(Set.empty[A])) { (acc, a) => - map2(acc, a)(_ + _) - } - - - def traverseUnorderedMap[K, L, A, B](sa: Map[K, A])(f: (K, A) => F[(L, B)]): F[Map[L, B]] = - sa.foldLeft(pure(Map.empty[L, B])) { (acc, a) => - map2(acc, f.tupled(a))(_ + _) - } - - def sequenceUnorderedMap[K, L, A](sa: Map[K, F[(K, A)]]): F[Map[K, A]] = { - sa.foldLeft(pure(Map.empty[K, A])) { (acc, a) => - map2(acc, a._2)(_ + _) - } - } -} - -@typeclass trait TraverseUnordered[F[_]] { - def traverseUnordered[G[_]: CommutativeApplicative, A, B](sa: F[A])(f: A => G[B]): G[F[B]] - - def sequenceUnordered[G[_]: CommutativeApplicative, A](fga: F[G[A]]): G[F[A]] = - traverseUnordered(fga)(identity) -} - -@typeclass trait NonEmptyTraverseUnordered[F[_]] { - def nonEmptyTraverseUnordered[G[_]: CommutativeApply, A, B](sa: F[A])(f: A => G[B]): G[F[B]] - - def nonEmptySequenceUnordered[G[_]: CommutativeApply, A](fga: F[G[A]]): G[F[A]] = - nonEmptyTraverseUnordered(fga)(identity) -} - -@typeclass trait NonEmptyCommutativeParallel[F[_], M[_]] { - def commutativeApply: CommutativeApply[F] - def commutativeFlatMap: CommutativeFlatMap[M] - - def sequential: F ~> M - def parallel: M ~> F -} - -@typeclass trait CommutativeParallel[F[_], M[_]] extends NonEmptyCommutativeParallel[F, M] { - def commutativeApplicative: CommutativeApplicative[F] - def commutativeMonad: CommutativeMonad[M] - - def commutativeApply = commutativeApplicative - def commutativeFlatMap = commutativeMonad -} +@typeclass trait CommutativeApplicative[F[_]] extends Applicative[F] with CommutativeApply[F] diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala new file mode 100644 index 0000000000..a6412983f2 --- /dev/null +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -0,0 +1,66 @@ +package cats + +import cats.kernel.CommutativeMonoid +import simulacrum.typeclass + +import scala.collection.mutable + +/** + * `UnorderedFoldable` is like a `Foldable` for unordered containers. + */ +@typeclass trait UnorderedFoldable[F[_]] { + + /** + * Left associative fold on 'F' using the function 'f'. + */ + def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B + + def unorderedFoldMap[A, B: CommutativeMonoid](fa: F[A])(f: A => B): B = + foldLeft(fa, Monoid[B].empty)((b, a) => Monoid[B].combine(b, f(a))) + + def unorderedFold[A: CommutativeMonoid](fa: F[A]): A = + unorderedFoldMap(fa)(identity) + + def toSet[A](fa: F[A]): Set[A] = + foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) => + buf += a + }.toSet + + + /** + * Returns true if there are no elements. Otherwise false. + */ + def isEmpty[A](fa: F[A]): Boolean = + foldLeft(fa, true)((_, _) => false) + + def nonEmpty[A](fa: F[A]): Boolean = + !isEmpty(fa) + + /** + * Find the first element matching the predicate, if one exists. + */ + def find[A](fa: F[A])(f: A => Boolean): Option[A] = + foldLeft(fa, Option.empty[A]) { (lb, a) => + if (f(a)) Some(a) else lb + } + + /** + * 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 = + foldLeft(fa, false) { (lb, a) => + if (p(a)) true else lb + } + + /** + * 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 = + foldLeft(fa, true) { (lb, a) => + if (p(a)) lb else false + } +} diff --git a/core/src/main/scala/cats/UnorderedTraverse.scala b/core/src/main/scala/cats/UnorderedTraverse.scala new file mode 100644 index 0000000000..e452a23878 --- /dev/null +++ b/core/src/main/scala/cats/UnorderedTraverse.scala @@ -0,0 +1,14 @@ +package cats + +import simulacrum.typeclass + +/** + * `UnorderedTraverse` is like a `Traverse` for unordered containers. In addition to the traverse and sequence + * methods it provides nonEmptyTraverse and nonEmptySequence methods which require an `Apply` instance instead of `Applicative`. + */ +@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 c364f6d60f..8814021d36 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,10 +16,10 @@ trait MapInstances extends cats.kernel.instances.MapInstances { } // scalastyle:off method.length - implicit def catsStdInstancesForMap[K]: Traverse[Map[K, ?]] with FlatMap[Map[K, ?]] = - new Traverse[Map[K, ?]] with FlatMap[Map[K, ?]] { + implicit def catsStdInstancesForMap[K]: UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] = + new UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] { - def traverse[G[_], A, B](fa: Map[K, A])(f: A => G[B])(implicit G: Applicative[G]): G[Map[K, B]] = { + 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.iterator, gba){ (kv, lbuf) => G.map2Eval(f(kv._2), lbuf)({ (b, buf) => buf + (kv._1 -> b)}) @@ -72,22 +74,13 @@ trait MapInstances extends cats.kernel.instances.MapInstances { bldr.result } - override def size[A](fa: Map[K, A]): Long = fa.size.toLong - - override def get[A](fa: Map[K, A])(idx: Long): Option[A] = - if (idx < 0L || Int.MaxValue < idx) None - else { - val n = idx.toInt - if (n >= fa.size) None - else Some(fa.valuesIterator.drop(n).next) - } override def isEmpty[A](fa: Map[K, A]): Boolean = fa.isEmpty - override def fold[A](fa: Map[K, A])(implicit A: Monoid[A]): A = + override def unorderedFold[A](fa: Map[K, A])(implicit A: CommutativeMonoid[A]): A = A.combineAll(fa.values) - override def toList[A](fa: Map[K, A]): List[A] = fa.values.toList + override def toSet[A](fa: Map[K, A]): Set[A] = fa.values.toSet } // 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 8cb380aa4c..770fb7a139 100644 --- a/core/src/main/scala/cats/instances/set.scala +++ b/core/src/main/scala/cats/instances/set.scala @@ -1,14 +1,24 @@ package cats package instances -import scala.annotation.tailrec +import cats.kernel.CommutativeMonoid import cats.syntax.show._ trait SetInstances extends cats.kernel.instances.SetInstances { - implicit val catsStdInstancesForSet: Foldable[Set] with MonoidK[Set] = - new Foldable[Set] with 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))(_ + _) + } + + def sequenceUnordered[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] @@ -20,36 +30,17 @@ trait SetInstances extends cats.kernel.instances.SetInstances { def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = Foldable.iterateRight(fa.iterator, lb)(f) - override def get[A](fa: Set[A])(idx: Long): Option[A] = { - @tailrec - def go(idx: Int, it: Iterator[A]): Option[A] = { - if (it.hasNext) { - if (idx == 0) Some(it.next) else { - it.next - go(idx - 1, it) - } - } else None - } - if (idx < Int.MaxValue && idx >= 0L) go(idx.toInt, fa.toIterator) else None - } + def insert[A](fa: Set[A], a: A): Set[A] = fa + a - override def size[A](fa: Set[A]): Long = fa.size.toLong + override def unorderedFold[A](fa: Set[A])(implicit A: CommutativeMonoid[A]): A = A.combineAll(fa) - override def exists[A](fa: Set[A])(p: A => Boolean): Boolean = - fa.exists(p) + override def toSet[A](fa: Set[A]): Set[A] = 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 - override def fold[A](fa: Set[A])(implicit A: Monoid[A]): A = A.combineAll(fa) - - override def toList[A](fa: Set[A]): List[A] = fa.toList - - override def reduceLeftOption[A](fa: Set[A])(f: (A, A) => A): Option[A] = - fa.reduceLeftOption(f) - override def find[A](fa: Set[A])(f: A => Boolean): Option[A] = fa.find(f) } 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..c09fc7abc8 --- /dev/null +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -0,0 +1,63 @@ +package cats +package laws + +import cats.implicits._ +import cats.kernel.CommutativeMonoid + +import scala.collection.mutable + + +trait UnorderedFoldableLaws[F[_]] { + implicit def F: UnorderedFoldable[F] + + def foldLeftConsistentWithUnorderedFoldMap[A, B](fa: F[A], f: A => B) + (implicit B: CommutativeMonoid[B]): IsEq[B] = + F.unorderedFoldMap(fa)(f) <-> F.foldLeft(fa, B.empty) { (b, a) => b |+| f(a) } + + def unorderedFoldConsistentWithUnorderedFoldMap[A: CommutativeMonoid](fa: F[A]): IsEq[A] = + F.unorderedFoldMap(fa)(identity) <-> F.unorderedFold(fa) + + def existsConsistentWithFind[A]( + fa: F[A], + p: A => Boolean + ): Boolean = { + F.exists(fa)(p) == F.find(fa)(p).isDefined + } + + 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) + } + + def toSetRef[A](fa: F[A]): IsEq[Set[A]] = + F.toSet(fa) <-> F.foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) => + buf += a + }.toSet + +} + +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/UnorderedFoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala new file mode 100644 index 0000000000..4f12d6d995 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala @@ -0,0 +1,39 @@ +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.set._ + +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, + "foldLeft consistent with unorderedFoldMap" -> forAll(laws.foldLeftConsistentWithUnorderedFoldMap[A, B] _), + "unorderedFold consistent with unorderedFoldMap" -> forAll(laws.unorderedFoldConsistentWithUnorderedFoldMap[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] _), + "toSet reference" -> forAll(laws.toSetRef[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/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index 4d399f6b8a..067d050783 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -206,14 +206,6 @@ class FoldableTestsAdditional extends CatsSuite { checkFoldMStackSafety[Vector](_.toVector) } - test("Foldable[Set].foldM stack safety") { - checkFoldMStackSafety[Set](_.toSet) - } - - test("Foldable[Map[String, ?]].foldM stack safety") { - checkFoldMStackSafety[Map[String, ?]](_.map(x => x.toString -> x).toMap) - } - test("Foldable[NonEmptyList].foldM stack safety") { checkFoldMStackSafety[NonEmptyList](xs => NonEmptyList.fromListUnsafe(xs.toList)) } @@ -311,18 +303,10 @@ class FoldableVectorCheck extends FoldableCheck[Vector]("vector") { def iterator[T](vector: Vector[T]): Iterator[T] = vector.iterator } -class FoldableSetCheck extends FoldableCheck[Set]("set") { - def iterator[T](set: Set[T]): Iterator[T] = set.iterator -} - class FoldableStreamCheck extends FoldableCheck[Stream]("stream") { def iterator[T](stream: Stream[T]): Iterator[T] = stream.iterator } -class FoldableMapCheck extends FoldableCheck[Map[Int, ?]]("map") { - def iterator[T](map: Map[Int, T]): Iterator[T] = map.valuesIterator -} - class FoldableOptionCheck extends FoldableCheck[Option]("option") { def iterator[T](option: Option[T]): Iterator[T] = option.iterator } diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index 6a3c955ce5..94759cdd6b 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -1,8 +1,7 @@ package cats package tests -import cats.data.{Tuple2K, Validated} -import cats.laws.discipline.{TraverseTests, FlatMapTests, SerializableTests, SemigroupalTests} +import cats.laws.discipline.{FlatMapTests, SemigroupalTests, SerializableTests, UnorderedTraverseTests} class MapTests extends CatsSuite { implicit val iso = SemigroupalTests.Isomorphisms.invariant[Map[Int, ?]] @@ -13,30 +12,9 @@ class MapTests 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", TraverseTests[Map[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) - checkAll("Traverse[Map[Int, ?]]", SerializableTests.serializable(Traverse[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("traverseUnordered identity") { - forAll { (mi: Map[Int, Int], f: (Int, Int) => (String, String)) => - CommutativeApplicative[Id].traverseUnorderedMap[Int, String, Int, String](mi)(f) should === (mi.map(f.tupled)) - } - } - - test("traverseUnordered parallel composition") { - forAll { (si: Map[Int, Int], f: (Int, Int) => Option[(String, String)], g: (Int, Int) => Option[(String, String)]) => - - val lhs = CommutativeApplicative[Tuple2K[Option, Option, ?]].traverseUnorderedMap(si)((i, j) => Tuple2K(f(i, j), g(i, j))) - val rhs = Tuple2K(CommutativeApplicative[Option].traverseUnorderedMap(si)(f), CommutativeApplicative[Option].traverseUnorderedMap(si)(g)) - lhs should ===(rhs) - } - } - - test("traverseUnordered consistent with sequenceUnordered") { - forAll { (mi: Map[Int, Int], f: (Int, Int) => (String, String)) => - CommutativeApplicative[Validated[Int, ?]].traverseUnorderedMap(mi)((i,j) => f(i, j).valid[Int]) should - === (CommutativeApplicative[Validated[Int, ?]].sequenceUnorderedMap(mi.map(i => (f.tupled(i)._1, f.tupled(i).valid[Int])))) - } - } test("show isn't empty and is formatted as expected") { forAll { (map: Map[Int, String]) => diff --git a/tests/src/test/scala/cats/tests/RegressionTests.scala b/tests/src/test/scala/cats/tests/RegressionTests.scala index be197e087b..53a55e4d73 100644 --- a/tests/src/test/scala/cats/tests/RegressionTests.scala +++ b/tests/src/test/scala/cats/tests/RegressionTests.scala @@ -102,11 +102,6 @@ class RegressionTests extends CatsSuite { Stream(1,2,6,8).traverse(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(3) - type StringMap[A] = Map[String, A] - val intMap: StringMap[Int] = Map("one" -> 1, "two" -> 2, "six" -> 6, "eight" -> 8) - intMap.traverse(validate) should === (Either.left("6 is greater than 5")) - checkAndResetCount(3) - NonEmptyList.of(1,2,6,8).traverse(validate) should === (Either.left("6 is greater than 5")) checkAndResetCount(3) diff --git a/tests/src/test/scala/cats/tests/SetTests.scala b/tests/src/test/scala/cats/tests/SetTests.scala index 71d96540af..8fd5d03c5f 100644 --- a/tests/src/test/scala/cats/tests/SetTests.scala +++ b/tests/src/test/scala/cats/tests/SetTests.scala @@ -1,17 +1,19 @@ package cats package tests -import cats.data.{Nested, Tuple2K, Validated} -import cats.laws.discipline.{FoldableTests, MonoidKTests, SerializableTests} -import cats.kernel.laws.discipline.{MonoidLawTests} +import cats.data.Validated +import cats.laws.discipline.{MonoidKTests, SerializableTests, UnorderedTraverseTests} +import cats.kernel.laws.discipline.MonoidLawTests +import cats.laws.discipline.arbitrary._ + class SetTests extends CatsSuite { checkAll("Set[Int]", MonoidLawTests[Set[Int]].monoid) checkAll("Set[Int]", MonoidKTests[Set].monoidK[Int]) checkAll("MonoidK[Set]", SerializableTests.serializable(MonoidK[Set])) - checkAll("Set[Int]", FoldableTests[Set].foldable[Int, Int]) - checkAll("Foldable[Set]", SerializableTests.serializable(Foldable[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)") @@ -22,35 +24,6 @@ class SetTests extends CatsSuite { } } - test("traverseUnordered identity") { - forAll { (si: Set[Int], f: Int => String) => - CommutativeApplicative[Id].traverseUnordered[Int, String](si)(f) should === (si.map(f)) - } - } - - test("traverseUnordered sequential composition") { - forAll { (si: Set[Int], f: Int => Option[String], g: String => Option[Int]) => - val lhs = Nested(CommutativeApplicative[Option].traverseUnordered(si)(f).map(ss => CommutativeApplicative[Option].traverseUnordered(ss)(g))) - val rhs = CommutativeApplicative[Nested[Option, Option, ?]].traverseUnordered(si)(i => Nested(f(i).map(g))) - lhs should === (rhs) - } - } - - test("traverseUnordered parallel composition") { - forAll { (si: Set[Int], f: Int => Option[String], g: Int => Option[String]) => - val lhs = CommutativeApplicative[Tuple2K[Option, Option, ?]].traverseUnordered(si)(i => Tuple2K(f(i), g(i))) - val rhs = Tuple2K(CommutativeApplicative[Option].traverseUnordered(si)(f), CommutativeApplicative[Option].traverseUnordered(si)(g)) - lhs should ===(rhs) - } - } - - test("traverseUnordered consistent with sequenceUnordered") { - forAll { (si: Set[Int], f: Int => String) => - CommutativeApplicative[Validated[Int, ?]].traverseUnordered(si)(i => f(i).valid[Int]) should - === (CommutativeApplicative[Validated[Int, ?]].sequenceUnordered(si.map(i => f(i).valid[Int]))) - } - } - test("show keeps separate entries for items that map to identical strings"){ //note: this val name has to be the same to shadow the cats.instances instance implicit val catsStdShowForInt: Show[Int] = Show.show(_ => "1")