From 0ea4ac07a3da4ad501b9c5756c9456ca71e6cec5 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 24 Sep 2017 17:45:07 +0200 Subject: [PATCH 01/27] Add CommutativeApply and Applicative and an instance for Validated[CommutativeSemigroup, ?] --- .../scala/cats/CommutativeApplicative.scala | 14 ++++++ .../main/scala/cats/CommutativeApply.scala | 14 ++++++ .../main/scala/cats/CommutativeFlatMap.scala | 2 +- .../main/scala/cats/CommutativeMonad.scala | 2 +- core/src/main/scala/cats/data/Validated.scala | 15 +++++++ .../laws/CommutativeApplicativeLaws.scala | 12 +++++ .../cats/laws/CommutativeApplyLaws.scala | 19 ++++++++ .../cats/laws/CommutativeFlatMapLaws.scala | 2 +- .../cats/laws/CommutativeMonadLaws.scala | 2 +- .../CommutativeApplicativeTests.scala | 42 +++++++++++++++++ .../discipline/CommutativeApplyTests.scala | 45 +++++++++++++++++++ .../discipline/CommutativeFlatMapTests.scala | 4 +- .../discipline/CommutativeMonadTests.scala | 4 +- .../scala/cats/tests/ValidatedTests.scala | 3 ++ 14 files changed, 172 insertions(+), 8 deletions(-) create mode 100644 core/src/main/scala/cats/CommutativeApplicative.scala create mode 100644 core/src/main/scala/cats/CommutativeApply.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeApplicativeLaws.scala create mode 100644 laws/src/main/scala/cats/laws/CommutativeApplyLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeApplicativeTests.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala diff --git a/core/src/main/scala/cats/CommutativeApplicative.scala b/core/src/main/scala/cats/CommutativeApplicative.scala new file mode 100644 index 0000000000..99ca3fcdfc --- /dev/null +++ b/core/src/main/scala/cats/CommutativeApplicative.scala @@ -0,0 +1,14 @@ +package cats + +import simulacrum.typeclass + +/** + * Commutative Applicative. + * + * Further than an Applicative, which just allows composition of independent effectful functions, + * in a Commutative Applicative those functions can be composed in any order, which guarantees + * that their effects do not interfere. + * + * Must obey the laws defined in cats.laws.CommutativeApplicativeLaws. + */ +@typeclass trait CommutativeApplicative[F[_]] extends Applicative[F] with CommutativeApply[F] diff --git a/core/src/main/scala/cats/CommutativeApply.scala b/core/src/main/scala/cats/CommutativeApply.scala new file mode 100644 index 0000000000..13a1974211 --- /dev/null +++ b/core/src/main/scala/cats/CommutativeApply.scala @@ -0,0 +1,14 @@ +package cats + +import simulacrum.typeclass + +/** + * Commutative Apply. + * + * Further than an Apply, which just allows composition of independent effectful functions, + * in a Commutative Apply those functions can be composed in any order, which guarantees + * that their effects do not interfere. + * + * Must obey the laws defined in cats.laws.CommutativeApplyLaws. + */ +@typeclass trait CommutativeApply[F[_]] extends Apply[F] diff --git a/core/src/main/scala/cats/CommutativeFlatMap.scala b/core/src/main/scala/cats/CommutativeFlatMap.scala index b7293de06b..22998bb3db 100644 --- a/core/src/main/scala/cats/CommutativeFlatMap.scala +++ b/core/src/main/scala/cats/CommutativeFlatMap.scala @@ -11,4 +11,4 @@ import simulacrum.typeclass * * Must obey the laws defined in cats.laws.CommutativeFlatMapLaws. */ -@typeclass trait CommutativeFlatMap[F[_]] extends FlatMap[F] +@typeclass trait CommutativeFlatMap[F[_]] extends FlatMap[F] with CommutativeApply[F] diff --git a/core/src/main/scala/cats/CommutativeMonad.scala b/core/src/main/scala/cats/CommutativeMonad.scala index 369541142a..9c7b07792d 100644 --- a/core/src/main/scala/cats/CommutativeMonad.scala +++ b/core/src/main/scala/cats/CommutativeMonad.scala @@ -11,4 +11,4 @@ import simulacrum.typeclass * * Must obey the laws defined in cats.laws.CommutativeMonadLaws. */ -@typeclass trait CommutativeMonad[F[_]] extends Monad[F] with CommutativeFlatMap[F] +@typeclass trait CommutativeMonad[F[_]] extends Monad[F] with CommutativeFlatMap[F] with CommutativeApplicative[F] diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index d195a7634d..1ba25e46c0 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -2,6 +2,7 @@ package cats package data import cats.data.Validated.{Invalid, Valid} +import cats.kernel.CommutativeSemigroup import scala.reflect.ClassTag import scala.util.{Failure, Success, Try} @@ -374,6 +375,20 @@ private[data] sealed abstract class ValidatedInstances1 extends ValidatedInstanc def combine(x: Validated[A, B], y: Validated[A, B]): Validated[A, B] = x combine y } + implicit def catsDataCommutativeApplicativeForValidated[E: CommutativeSemigroup]: CommutativeApplicative[Validated[E, ?]] = + new CommutativeApplicative[Validated[E, ?]] { + override def map[A, B](fa: Validated[E, A])(f: A => B): Validated[E, B] = + fa.map(f) + + def pure[A](a: A): Validated[E, A] = Validated.valid(a) + + def ap[A, B](ff: Validated[E, (A) => B])(fa: Validated[E, A]): Validated[E, B] = + fa.ap(ff)(Semigroup[E]) + + override def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = + fa.product(fb)(Semigroup[E]) + } + implicit def catsDataPartialOrderForValidated[A: PartialOrder, B: PartialOrder]: PartialOrder[Validated[A, B]] = new PartialOrder[Validated[A, B]] { def partialCompare(x: Validated[A, B], y: Validated[A, B]): Double = x partialCompare y diff --git a/laws/src/main/scala/cats/laws/CommutativeApplicativeLaws.scala b/laws/src/main/scala/cats/laws/CommutativeApplicativeLaws.scala new file mode 100644 index 0000000000..05f9770312 --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeApplicativeLaws.scala @@ -0,0 +1,12 @@ +package cats.laws + +import cats.CommutativeApplicative + +trait CommutativeApplicativeLaws[F[_]] extends CommutativeApplyLaws[F] with ApplicativeLaws[F] { + implicit override def F: CommutativeApplicative[F] +} + +object CommutativeApplicativeLaws { + def apply[F[_]](implicit ev: CommutativeApplicative[F]): CommutativeApplicativeLaws[F] = + new CommutativeApplicativeLaws[F] { def F: CommutativeApplicative[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/CommutativeApplyLaws.scala b/laws/src/main/scala/cats/laws/CommutativeApplyLaws.scala new file mode 100644 index 0000000000..cbc2a54e3e --- /dev/null +++ b/laws/src/main/scala/cats/laws/CommutativeApplyLaws.scala @@ -0,0 +1,19 @@ +package cats.laws + +import cats.CommutativeApply + +/** + * Laws that must be obeyed by any `CommutativeApply`. + */ +trait CommutativeApplyLaws[F[_]] extends ApplyLaws[F] { + implicit override def F: CommutativeApply[F] + + def applyCommutative[A, B, C](fa: F[A], fb: F[B], f: (A, B) => C): IsEq[F[C]] = + F.map2(fa, fb)(f) <-> F.map2(fb, fa)((b, a) => f(a, b)) + +} + +object CommutativeApplyLaws { + def apply[F[_]](implicit ev: CommutativeApply[F]): CommutativeApplyLaws[F] = + new CommutativeApplyLaws[F] { def F: CommutativeApply[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala b/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala index 321183097c..1ed46cd511 100644 --- a/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala +++ b/laws/src/main/scala/cats/laws/CommutativeFlatMapLaws.scala @@ -4,7 +4,7 @@ package laws /** * Laws that must be obeyed by any `CommutativeFlatMap`. */ -trait CommutativeFlatMapLaws[F[_]] extends FlatMapLaws[F] { +trait CommutativeFlatMapLaws[F[_]] extends CommutativeApplyLaws[F] with FlatMapLaws[F] { implicit override def F: CommutativeFlatMap[F] def flatmapCommutative[A, B, C](fa: F[A], fb: F[B], g: (A, B) => F[C]): IsEq[F[C]] = diff --git a/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala b/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala index 1172b30165..7333a994ef 100644 --- a/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala +++ b/laws/src/main/scala/cats/laws/CommutativeMonadLaws.scala @@ -4,7 +4,7 @@ package laws /** * Laws that must be obeyed by any `CommutativeMonad`. */ -trait CommutativeMonadLaws[F[_]] extends MonadLaws[F] with CommutativeFlatMapLaws[F] { +trait CommutativeMonadLaws[F[_]] extends MonadLaws[F] with CommutativeFlatMapLaws[F] with CommutativeApplicativeLaws[F] { implicit override def F: CommutativeMonad[F] } diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeApplicativeTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeApplicativeTests.scala new file mode 100644 index 0000000000..4b1359d04e --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeApplicativeTests.scala @@ -0,0 +1,42 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.CartesianTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen, Prop} + +trait CommutativeApplicativeTests[F[_]] extends CommutativeApplyTests[F] with ApplicativeTests[F] { + + def laws: CommutativeApplicativeLaws[F] + + def commutativeApplicative[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + EqFInt: Eq[F[Int]], + iso: Isomorphisms[F] + ): RuleSet = { + new RuleSet { + def name: String = "commutative applicative" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(applicative[A, B, C], commutativeApply[A, B, C]) + def props: Seq[(String, Prop)] = Nil + } + } +} + +object CommutativeApplicativeTests { + def apply[F[_]: CommutativeApplicative]: CommutativeApplicativeTests[F] = + new CommutativeApplicativeTests[F] { + def laws: CommutativeApplicativeLaws[F] = CommutativeApplicativeLaws[F] + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala new file mode 100644 index 0000000000..4946a90f64 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala @@ -0,0 +1,45 @@ +package cats +package laws +package discipline + +import cats.laws.discipline.CartesianTests.Isomorphisms +import org.scalacheck.{Arbitrary, Cogen, Prop} +import Prop._ + +trait CommutativeApplyTests[F[_]] extends ApplyTests[F] { + def laws: CommutativeApplyLaws[F] + + def commutativeApply[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit + ArbFA: Arbitrary[F[A]], + ArbFB: Arbitrary[F[B]], + ArbFC: Arbitrary[F[C]], + ArbFAtoB: Arbitrary[F[A => B]], + ArbFBtoC: Arbitrary[F[B => C]], + CogenA: Cogen[A], + CogenB: Cogen[B], + CogenC: Cogen[C], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqFABC: Eq[F[(A, B, C)]], + EqFInt: Eq[F[Int]], + iso: Isomorphisms[F] + ): RuleSet = { + new RuleSet { + def name: String = "commutative apply" + def bases: Seq[(String, RuleSet)] = Nil + def parents: Seq[RuleSet] = Seq(apply[A, B, C]) + def props: Seq[(String, Prop)] = Seq( + "apply commutativity" -> forAll(laws.applyCommutative[A, B, C] _) + ) + } + } + +} + +object CommutativeApplyTests { + def apply[F[_]: CommutativeFlatMap]: CommutativeApplyTests[F] = + new CommutativeApplyTests[F] { + def laws: CommutativeApplyLaws[F] = CommutativeApplyLaws[F] + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala index 70a69fe2bd..87e1168bd8 100644 --- a/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeFlatMapTests.scala @@ -6,7 +6,7 @@ import cats.laws.discipline.CartesianTests.Isomorphisms import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ -trait CommutativeFlatMapTests[F[_]] extends FlatMapTests[F] { +trait CommutativeFlatMapTests[F[_]] extends FlatMapTests[F] with CommutativeApplyTests[F] { def laws: CommutativeFlatMapLaws[F] def commutativeFlatMap[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -28,7 +28,7 @@ trait CommutativeFlatMapTests[F[_]] extends FlatMapTests[F] { new RuleSet { def name: String = "commutative flatMap" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(flatMap[A, B, C]) + def parents: Seq[RuleSet] = Seq(flatMap[A, B, C], commutativeApply[A, B, C]) def props: Seq[(String, Prop)] = Seq( "flatmap commutativity" -> forAll(laws.flatmapCommutative[A, B, C] _) ) diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala index c2a0975759..4114e75d5b 100644 --- a/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeMonadTests.scala @@ -5,7 +5,7 @@ package discipline import cats.laws.discipline.CartesianTests.Isomorphisms import org.scalacheck.{Arbitrary, Cogen, Prop} -trait CommutativeMonadTests[F[_]] extends MonadTests[F] with CommutativeFlatMapTests[F] { +trait CommutativeMonadTests[F[_]] extends MonadTests[F] with CommutativeFlatMapTests[F] with CommutativeApplicativeTests[F] { def laws: CommutativeMonadLaws[F] def commutativeMonad[A: Arbitrary: Eq, B: Arbitrary: Eq, C: Arbitrary: Eq](implicit @@ -27,7 +27,7 @@ trait CommutativeMonadTests[F[_]] extends MonadTests[F] with CommutativeFlatMapT new RuleSet { def name: String = "commutative monad" def bases: Seq[(String, RuleSet)] = Nil - def parents: Seq[RuleSet] = Seq(monad[A, B, C], commutativeFlatMap[A, B, C]) + def parents: Seq[RuleSet] = Seq(monad[A, B, C], commutativeFlatMap[A, B, C], commutativeApplicative[A, B, C]) def props: Seq[(String, Prop)] = Nil } } diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index dc152a5340..c4b07a9009 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -34,6 +34,9 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, NonEmptyList[Int]]", GroupLaws[Validated[String, NonEmptyList[Int]]].semigroup) + checkAll("Validated[Int, Int]", CommutativeApplicativeTests[Validated[Int, ?]].commutativeApplicative[Int, Int, Int]) + checkAll("CommutativeApplicative[Validated[Int, ?]]", SerializableTests.serializable(CommutativeApplicative[Validated[Int, ?]])) + { implicit val L = ListWrapper.semigroup[String] checkAll("Validated[ListWrapper[String], ?]", SemigroupKTests[Validated[ListWrapper[String], ?]].semigroupK[Int]) From 0cd4eb909c9d91bbb0805c10e0263e3315f24522 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 28 Sep 2017 09:22:14 +0200 Subject: [PATCH 02/27] Make Future a CommutativeApplicative --- core/src/main/scala/cats/instances/future.scala | 4 ++-- js/src/test/scala/cats/tests/FutureTests.scala | 1 + jvm/src/test/scala/cats/tests/FutureTests.scala | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index 2c62956816..5b3ad1e62e 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -6,8 +6,8 @@ import scala.concurrent.{ExecutionContext, Future} trait FutureInstances extends FutureInstances1 { - implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] = - new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] with StackSafeMonad[Future] { + implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] with CommutativeApplicative[Future] = + new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] with StackSafeMonad[Future] with CommutativeApplicative[Future] { def pure[A](x: A): Future[A] = Future.successful(x) def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 61f4c1010d..50f95d95ca 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -55,6 +55,7 @@ class FutureTests extends CatsSuite { checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) + checkAll("Future", CommutativeApplicativeTests[Future].commutativeApplicative[Int, Int, Int]) { implicit val F = ListWrapper.semigroup[Int] diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index bf011071a8..634e0246af 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -40,6 +40,7 @@ class FutureTests extends CatsSuite { checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) + checkAll("Future", CommutativeApplicativeTests[Future].commutativeApplicative[Int, Int, Int]) checkAll("Future", CoflatMapTests[Future].coflatMap[Int, Int, Int]) { From f9a239a0b01b3954e1756abf56deffca1e89743c Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 28 Sep 2017 18:06:29 +0200 Subject: [PATCH 03/27] Revert "Make Future a CommutativeApplicative" This reverts commit 0cd4eb909c9d91bbb0805c10e0263e3315f24522. --- core/src/main/scala/cats/instances/future.scala | 4 ++-- js/src/test/scala/cats/tests/FutureTests.scala | 1 - jvm/src/test/scala/cats/tests/FutureTests.scala | 1 - 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/instances/future.scala b/core/src/main/scala/cats/instances/future.scala index 5b3ad1e62e..2c62956816 100644 --- a/core/src/main/scala/cats/instances/future.scala +++ b/core/src/main/scala/cats/instances/future.scala @@ -6,8 +6,8 @@ import scala.concurrent.{ExecutionContext, Future} trait FutureInstances extends FutureInstances1 { - implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] with CommutativeApplicative[Future] = - new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] with StackSafeMonad[Future] with CommutativeApplicative[Future] { + implicit def catsStdInstancesForFuture(implicit ec: ExecutionContext): MonadError[Future, Throwable] with CoflatMap[Future] with Monad[Future] = + new FutureCoflatMap with MonadError[Future, Throwable] with Monad[Future] with StackSafeMonad[Future] { def pure[A](x: A): Future[A] = Future.successful(x) def flatMap[A, B](fa: Future[A])(f: A => Future[B]): Future[B] = fa.flatMap(f) diff --git a/js/src/test/scala/cats/tests/FutureTests.scala b/js/src/test/scala/cats/tests/FutureTests.scala index 50f95d95ca..61f4c1010d 100644 --- a/js/src/test/scala/cats/tests/FutureTests.scala +++ b/js/src/test/scala/cats/tests/FutureTests.scala @@ -55,7 +55,6 @@ class FutureTests extends CatsSuite { checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future[Int]", ComonadTests[Future].comonad[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) - checkAll("Future", CommutativeApplicativeTests[Future].commutativeApplicative[Int, Int, Int]) { implicit val F = ListWrapper.semigroup[Int] diff --git a/jvm/src/test/scala/cats/tests/FutureTests.scala b/jvm/src/test/scala/cats/tests/FutureTests.scala index 634e0246af..bf011071a8 100644 --- a/jvm/src/test/scala/cats/tests/FutureTests.scala +++ b/jvm/src/test/scala/cats/tests/FutureTests.scala @@ -40,7 +40,6 @@ class FutureTests extends CatsSuite { checkAll("Future with Throwable", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int]) checkAll("Future", MonadTests[Future].monad[Int, Int, Int]) - checkAll("Future", CommutativeApplicativeTests[Future].commutativeApplicative[Int, Int, Int]) checkAll("Future", CoflatMapTests[Future].coflatMap[Int, Int, Int]) { From 965f9c26edb58702173b997301f472e8c0343da7 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 30 Sep 2017 18:46:23 +0200 Subject: [PATCH 04/27] Add unordered traversal for Sets --- .../main/scala/cats/CommutativeApplicative.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/src/main/scala/cats/CommutativeApplicative.scala b/core/src/main/scala/cats/CommutativeApplicative.scala index 99ca3fcdfc..60f2ca5d14 100644 --- a/core/src/main/scala/cats/CommutativeApplicative.scala +++ b/core/src/main/scala/cats/CommutativeApplicative.scala @@ -12,3 +12,18 @@ import simulacrum.typeclass * Must obey the laws defined in cats.laws.CommutativeApplicativeLaws. */ @typeclass trait CommutativeApplicative[F[_]] extends Applicative[F] with CommutativeApply[F] + + +object CommutativeApplicative { + + def traverseUnordered[F[_]: CommutativeApplicative, A, B](sa: Set[A])(f: A => F[B]): F[Set[B]] = + sa.foldLeft(Applicative[F].pure(Set.empty[B])) { (buf, a) => + Applicative[F].map2(buf, f(a))(_ + _) + } + + def sequenceUnordered[F[_]: CommutativeApplicative, A](sa: Set[F[A]]): F[Set[A]] = + sa.foldLeft(Applicative[F].pure(Set.empty[A])) { (acc, a) => + Applicative[F].map2(acc, a)(_ + _) + } + +} From d3a803ac6ee881a4261f2b53eb82e3d594957f74 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 30 Sep 2017 20:27:22 +0200 Subject: [PATCH 05/27] Test commutativeApply instances + Nested + Tuple2K instances --- .../scala/cats/CommutativeApplicative.scala | 4 +-- core/src/main/scala/cats/data/Nested.scala | 16 +++++++++- core/src/main/scala/cats/data/Tuple2K.scala | 20 +++++++++++- .../discipline/CommutativeApplyTests.scala | 2 +- .../test/scala/cats/tests/NestedTests.scala | 13 ++++++++ .../src/test/scala/cats/tests/SetTests.scala | 32 +++++++++++++++++++ .../test/scala/cats/tests/Tuple2KTests.scala | 12 ++++++- 7 files changed, 93 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/cats/CommutativeApplicative.scala b/core/src/main/scala/cats/CommutativeApplicative.scala index 60f2ca5d14..5031229c09 100644 --- a/core/src/main/scala/cats/CommutativeApplicative.scala +++ b/core/src/main/scala/cats/CommutativeApplicative.scala @@ -17,8 +17,8 @@ import simulacrum.typeclass object CommutativeApplicative { def traverseUnordered[F[_]: CommutativeApplicative, A, B](sa: Set[A])(f: A => F[B]): F[Set[B]] = - sa.foldLeft(Applicative[F].pure(Set.empty[B])) { (buf, a) => - Applicative[F].map2(buf, f(a))(_ + _) + sa.foldLeft(Applicative[F].pure(Set.empty[B])) { (acc, a) => + Applicative[F].map2(acc, f(a))(_ + _) } def sequenceUnordered[F[_]: CommutativeApplicative, A](sa: Set[F[A]]): F[Set[A]] = diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index 956ec67773..4ff3a51920 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -135,13 +135,27 @@ private[data] sealed abstract class NestedInstances9 extends NestedInstances10 { } } -private[data] sealed abstract class NestedInstances10 { +private[data] sealed abstract class NestedInstances10 extends NestedInstances11 { implicit def catsDataInvariantForNestedContravariant[F[_]: Invariant, G[_]: Contravariant]: Invariant[Nested[F, G, ?]] = new NestedInvariant[F, G] { val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].composeContravariant[G] } } +private[data] sealed abstract class NestedInstances11 extends NestedInstances12 { + implicit def catsDataCommutativeApplyForNestedContravariant[F[_]: CommutativeApply, G[_]: CommutativeApply]: CommutativeApply[Nested[F, G, ?]] = + new NestedApply[F, G] with CommutativeApply[Nested[F, G, ?]] { + val FG: Apply[λ[α => F[G[α]]]] = Apply[F].compose[G] + } +} + +private[data] sealed abstract class NestedInstances12 { + implicit def catsDataCommutativeApplicativeForNestedContravariant[F[_]: CommutativeApplicative, G[_]: CommutativeApplicative]: CommutativeApplicative[Nested[F, G, ?]] = + new NestedApplicative[F, G] with CommutativeApplicative[Nested[F, G, ?]] { + val FG: Applicative[λ[α => F[G[α]]]] = Applicative[F].compose[G] + } +} + private[data] trait NestedInvariant[F[_], G[_]] extends Invariant[Nested[F, G, ?]] { def FG: Invariant[λ[α => F[G[α]]]] diff --git a/core/src/main/scala/cats/data/Tuple2K.scala b/core/src/main/scala/cats/data/Tuple2K.scala index 28a40bbc8e..633c003083 100644 --- a/core/src/main/scala/cats/data/Tuple2K.scala +++ b/core/src/main/scala/cats/data/Tuple2K.scala @@ -78,13 +78,31 @@ private[data] sealed abstract class Tuple2KInstances4 extends Tuple2KInstances5 } } -private[data] sealed abstract class Tuple2KInstances5 { +private[data] sealed abstract class Tuple2KInstances5 extends Tuple2KInstances6 { implicit def catsDataFunctorForTuple2K[F[_], G[_]](implicit FF: Functor[F], GG: Functor[G]): Functor[λ[α => Tuple2K[F, G, α]]] = new Tuple2KFunctor[F, G] { def F: Functor[F] = FF def G: Functor[G] = GG } } + +private[data] sealed abstract class Tuple2KInstances6 extends Tuple2KInstances7 { + + implicit def catsDataCommutativeApplyForTuple2K[F[_], G[_]](implicit FF: CommutativeApply[F], GG: CommutativeApply[G]): CommutativeApply[λ[α => Tuple2K[F, G, α]]] = + new Tuple2KApply[F, G] with CommutativeApply[λ[α => Tuple2K[F, G, α]]] { + def F: Apply[F] = FF + def G: Apply[G] = GG + } +} + +private[data] sealed abstract class Tuple2KInstances7 { + implicit def catsDataCommutativeApplicativeForTuple2K[F[_], G[_]](implicit FF: CommutativeApplicative[F], GG: CommutativeApplicative[G]): CommutativeApplicative[λ[α => Tuple2K[F, G, α]]] = + new Tuple2KApplicative[F, G] with CommutativeApplicative[λ[α => Tuple2K[F, G, α]]] { + def F: Applicative[F] = FF + def G: Applicative[G] = GG + } +} + private[data] sealed trait Tuple2KFunctor[F[_], G[_]] extends Functor[λ[α => Tuple2K[F, G, α]]] { def F: Functor[F] def G: Functor[G] diff --git a/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala b/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala index 4946a90f64..cd272ffd41 100644 --- a/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/CommutativeApplyTests.scala @@ -38,7 +38,7 @@ trait CommutativeApplyTests[F[_]] extends ApplyTests[F] { } object CommutativeApplyTests { - def apply[F[_]: CommutativeFlatMap]: CommutativeApplyTests[F] = + def apply[F[_]: CommutativeApply]: CommutativeApplyTests[F] = new CommutativeApplyTests[F] { def laws: CommutativeApplyLaws[F] = CommutativeApplyLaws[F] } diff --git a/tests/src/test/scala/cats/tests/NestedTests.scala b/tests/src/test/scala/cats/tests/NestedTests.scala index b848137765..59d1a8f37f 100644 --- a/tests/src/test/scala/cats/tests/NestedTests.scala +++ b/tests/src/test/scala/cats/tests/NestedTests.scala @@ -70,6 +70,12 @@ class NestedTests extends CatsSuite { checkAll("Apply[Nested[List, ListWrapper, ?]]", SerializableTests.serializable(Apply[Nested[List, ListWrapper, ?]])) } + { + // CommutativeApply composition + checkAll("Nested[Option, Validated[Int, ?], ?]", CommutativeApplyTests[Nested[Option, Validated[Int, ?], ?]].commutativeApply[Int, Int, Int]) + checkAll("CommutativeApply[Nested[List, ListWrapper, ?]]", SerializableTests.serializable(CommutativeApply[Nested[Option, Validated[Int, ?], ?]])) + } + { // Applicative composition implicit val instance = ListWrapper.applicative @@ -77,6 +83,13 @@ class NestedTests extends CatsSuite { checkAll("Applicative[Nested[List, ListWrapper, ?]]", SerializableTests.serializable(Applicative[Nested[List, ListWrapper, ?]])) } + { + // CommutativeApplicative composition + implicit val instance = ListWrapper.applicative + checkAll("Nested[Option, Validated[Int, ?], ?]", CommutativeApplicativeTests[Nested[Option, Validated[Int, ?], ?]].commutativeApplicative[Int, Int, Int]) + checkAll("CommutativeApplicative[Nested[List, ListWrapper, ?]]", SerializableTests.serializable(CommutativeApplicative[Nested[Option, Validated[Int, ?], ?]])) + } + { //ApplicativeError composition implicit val instance = ListWrapper.applicative diff --git a/tests/src/test/scala/cats/tests/SetTests.scala b/tests/src/test/scala/cats/tests/SetTests.scala index 3dfb705582..74155eadde 100644 --- a/tests/src/test/scala/cats/tests/SetTests.scala +++ b/tests/src/test/scala/cats/tests/SetTests.scala @@ -1,6 +1,7 @@ package cats package tests +import cats.data.{Nested, Tuple2K} import cats.laws.discipline.{FoldableTests, MonoidKTests, SerializableTests} class SetTests extends CatsSuite { @@ -21,6 +22,37 @@ class SetTests extends CatsSuite { } } + test("traverseUnordered identity") { + forAll { (si: Set[Int], f: Int => String) => + CommutativeApplicative.traverseUnordered[Id, 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.traverseUnordered(si)(f).map(ss => CommutativeApplicative.traverseUnordered(ss)(g))) + val rhs = CommutativeApplicative.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]) => + type Two[A] = Tuple2K[Option, Option, A] + + val lhs = CommutativeApplicative.traverseUnordered(si)(i => Tuple2K(f(i), g(i))) + val rhs = Tuple2K(CommutativeApplicative.traverseUnordered(si)(f), CommutativeApplicative.traverseUnordered(si)(g)) + lhs should ===(rhs) + } + } + + test("traverseUnordered consistent with sequenceUnordered") { + forAll { (si: Set[Int], f: Int => String) => + CommutativeApplicative.traverseUnordered(si)(i => f(i).valid[Int]) should + === (CommutativeApplicative.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") diff --git a/tests/src/test/scala/cats/tests/Tuple2KTests.scala b/tests/src/test/scala/cats/tests/Tuple2KTests.scala index fa041999fa..b69f383571 100644 --- a/tests/src/test/scala/cats/tests/Tuple2KTests.scala +++ b/tests/src/test/scala/cats/tests/Tuple2KTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.Tuple2K +import cats.data.{Tuple2K, Validated} import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ @@ -40,6 +40,16 @@ class Tuple2KTests extends CatsSuite { checkAll("Apply[Tuple2K[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(Apply[Tuple2K[ListWrapper, ListWrapper, ?]])) } + { + checkAll("Tuple2K[Option, Validated[Int, ?], ?]", CommutativeApplyTests[Tuple2K[Option, Validated[Int, ?], ?]].commutativeApply[Int, Int, Int]) + checkAll("Apply[Tuple2K[Option, Validated[Int, ?], ?]]", SerializableTests.serializable(CommutativeApply[Tuple2K[Option, Validated[Int, ?], ?]])) + } + + { + checkAll("Tuple2K[Option, Validated[Int, ?], ?]", CommutativeApplicativeTests[Tuple2K[Option, Validated[Int, ?], ?]].commutativeApplicative[Int, Int, Int]) + checkAll("Applicative[Tuple2K[Option, Validated[Int, ?], ?]]", SerializableTests.serializable(CommutativeApplicative[Tuple2K[Option, Validated[Int, ?], ?]])) + } + { implicit val functor = ListWrapper.functor checkAll("Tuple2K[ListWrapper, ListWrapper, ?]", FunctorTests[Tuple2K[ListWrapper, ListWrapper, ?]].functor[Int, Int, Int]) From 1ab4f47f7c087fec334d0f81254fefad16bda358 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 2 Oct 2017 19:17:07 +0200 Subject: [PATCH 06/27] Add unordered traversal for Map --- .../scala/cats/CommutativeApplicative.scala | 12 +++++++++ .../src/test/scala/cats/tests/MapTests.scala | 25 ++++++++++++++++++- .../src/test/scala/cats/tests/SetTests.scala | 2 -- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/CommutativeApplicative.scala b/core/src/main/scala/cats/CommutativeApplicative.scala index 5031229c09..d524388784 100644 --- a/core/src/main/scala/cats/CommutativeApplicative.scala +++ b/core/src/main/scala/cats/CommutativeApplicative.scala @@ -26,4 +26,16 @@ object CommutativeApplicative { Applicative[F].map2(acc, a)(_ + _) } + + def traverseUnorderedMap[F[_]: CommutativeApplicative, K, L, A, B](sa: Map[K, A])(f: (K, A) => F[(L, B)]): F[Map[L, B]] = + sa.foldLeft(Applicative[F].pure(Map.empty[L, B])) { (acc, a) => + Applicative[F].map2(acc, f.tupled(a))(_ + _) + } + + def sequenceUnorderedMap[F[_]: CommutativeApplicative, K, L, A](sa: Map[K, F[(K, A)]]): F[Map[K, A]] = { + sa.foldLeft(Applicative[F].pure(Map.empty[K, A])) { (acc, a) => + Applicative[F].map2(acc, a._2)(_ + _) + } + } + } diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index fb33354c64..75ae3f3dfa 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -1,7 +1,8 @@ package cats package tests -import cats.laws.discipline.{TraverseTests, FlatMapTests, SerializableTests, CartesianTests} +import cats.data.Tuple2K +import cats.laws.discipline.{CartesianTests, FlatMapTests, SerializableTests, TraverseTests} class MapTests extends CatsSuite { implicit val iso = CartesianTests.Isomorphisms.invariant[Map[Int, ?]] @@ -15,6 +16,28 @@ class MapTests extends CatsSuite { 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, ?]])) + test("traverseUnordered identity") { + forAll { (mi: Map[Int, Int], f: (Int, Int) => (String, String)) => + CommutativeApplicative.traverseUnorderedMap[Id, 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.traverseUnorderedMap(si)((i, j) => Tuple2K(f(i, j), g(i, j))) + val rhs = Tuple2K(CommutativeApplicative.traverseUnorderedMap(si)(f), CommutativeApplicative.traverseUnorderedMap(si)(g)) + lhs should ===(rhs) + } + } + + test("traverseUnordered consistent with sequenceUnordered") { + forAll { (mi: Map[Int, Int], f: (Int, Int) => (String, String)) => + CommutativeApplicative.traverseUnorderedMap(mi)((i,j) => f(i, j).valid[Int]) should + === (CommutativeApplicative.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]) => map.show.nonEmpty should === (true) diff --git a/tests/src/test/scala/cats/tests/SetTests.scala b/tests/src/test/scala/cats/tests/SetTests.scala index 74155eadde..cc1dcf32a1 100644 --- a/tests/src/test/scala/cats/tests/SetTests.scala +++ b/tests/src/test/scala/cats/tests/SetTests.scala @@ -38,8 +38,6 @@ class SetTests extends CatsSuite { test("traverseUnordered parallel composition") { forAll { (si: Set[Int], f: Int => Option[String], g: Int => Option[String]) => - type Two[A] = Tuple2K[Option, Option, A] - val lhs = CommutativeApplicative.traverseUnordered(si)(i => Tuple2K(f(i), g(i))) val rhs = Tuple2K(CommutativeApplicative.traverseUnordered(si)(f), CommutativeApplicative.traverseUnordered(si)(g)) lhs should ===(rhs) From 2164b51a7910ebd7833fa657b340e8450f6b2875 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 2 Oct 2017 19:27:36 +0200 Subject: [PATCH 07/27] Fix priority for Nested instances --- core/src/main/scala/cats/data/Nested.scala | 44 +++++++++++----------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index 4ff3a51920..723398b222 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -50,7 +50,7 @@ private[data] sealed abstract class NestedInstances1 extends NestedInstances2 { val FG: Reducible[λ[α => F[G[α]]]] = Reducible[F].compose[G] } - implicit def catsDataContravariantForNested[F[_]: Contravariant, G[_]: Contravariant]: Functor[Nested[F, G, ?]] = + implicit def catsDataFunctorForContravariantForNested[F[_]: Contravariant, G[_]: Contravariant]: Functor[Nested[F, G, ?]] = new NestedFunctor[F, G] { val FG: Functor[λ[α => F[G[α]]]] = Contravariant[F].compose[G] } @@ -91,8 +91,8 @@ private[data] sealed abstract class NestedInstances4 extends NestedInstances5 { } private[data] sealed abstract class NestedInstances5 extends NestedInstances6 { - implicit def catsDataApplicativeForNested[F[_]: Applicative, G[_]: Applicative]: Applicative[Nested[F, G, ?]] = - new NestedApplicative[F, G] { + implicit def catsDataCommutativeApplicativeForNestedContravariant[F[_]: CommutativeApplicative, G[_]: CommutativeApplicative]: CommutativeApplicative[Nested[F, G, ?]] = + new NestedApplicative[F, G] with CommutativeApplicative[Nested[F, G, ?]] { val FG: Applicative[λ[α => F[G[α]]]] = Applicative[F].compose[G] } @@ -103,8 +103,8 @@ private[data] sealed abstract class NestedInstances5 extends NestedInstances6 { } private[data] sealed abstract class NestedInstances6 extends NestedInstances7 { - implicit def catsDataApplyForNested[F[_]: Apply, G[_]: Apply]: Apply[Nested[F, G, ?]] = - new NestedApply[F, G] { + implicit def catsDataCommutativeApplyForNestedContravariant[F[_]: CommutativeApply, G[_]: CommutativeApply]: CommutativeApply[Nested[F, G, ?]] = + new NestedApply[F, G] with CommutativeApply[Nested[F, G, ?]] { val FG: Apply[λ[α => F[G[α]]]] = Apply[F].compose[G] } @@ -115,44 +115,44 @@ private[data] sealed abstract class NestedInstances6 extends NestedInstances7 { } private[data] sealed abstract class NestedInstances7 extends NestedInstances8 { - implicit def catsDataFunctorForNested[F[_]: Functor, G[_]: Functor]: Functor[Nested[F, G, ?]] = - new NestedFunctor[F, G] { - val FG: Functor[λ[α => F[G[α]]]] = Functor[F].compose[G] + implicit def catsDataApplicativeForNested[F[_]: Applicative, G[_]: Applicative]: Applicative[Nested[F, G, ?]] = + new NestedApplicative[F, G] { + val FG: Applicative[λ[α => F[G[α]]]] = Applicative[F].compose[G] } } private[data] sealed abstract class NestedInstances8 extends NestedInstances9 { - implicit def catsDataInvariantForNested[F[_]: Invariant, G[_]: Invariant]: Invariant[Nested[F, G, ?]] = - new NestedInvariant[F, G] { - val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].compose[G] + implicit def catsDataApplyForNested[F[_]: Apply, G[_]: Apply]: Apply[Nested[F, G, ?]] = + new NestedApply[F, G] { + val FG: Apply[λ[α => F[G[α]]]] = Apply[F].compose[G] } } private[data] sealed abstract class NestedInstances9 extends NestedInstances10 { - implicit def catsDataInvariantForCovariantNested[F[_]: Invariant, G[_]: Functor]: Invariant[Nested[F, G, ?]] = - new NestedInvariant[F, G] { - val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].composeFunctor[G] + implicit def catsDataFunctorForNested[F[_]: Functor, G[_]: Functor]: Functor[Nested[F, G, ?]] = + new NestedFunctor[F, G] { + val FG: Functor[λ[α => F[G[α]]]] = Functor[F].compose[G] } } private[data] sealed abstract class NestedInstances10 extends NestedInstances11 { - implicit def catsDataInvariantForNestedContravariant[F[_]: Invariant, G[_]: Contravariant]: Invariant[Nested[F, G, ?]] = + implicit def catsDataInvariantForNested[F[_]: Invariant, G[_]: Invariant]: Invariant[Nested[F, G, ?]] = new NestedInvariant[F, G] { - val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].composeContravariant[G] + val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].compose[G] } } private[data] sealed abstract class NestedInstances11 extends NestedInstances12 { - implicit def catsDataCommutativeApplyForNestedContravariant[F[_]: CommutativeApply, G[_]: CommutativeApply]: CommutativeApply[Nested[F, G, ?]] = - new NestedApply[F, G] with CommutativeApply[Nested[F, G, ?]] { - val FG: Apply[λ[α => F[G[α]]]] = Apply[F].compose[G] + implicit def catsDataInvariantForCovariantNested[F[_]: Invariant, G[_]: Functor]: Invariant[Nested[F, G, ?]] = + new NestedInvariant[F, G] { + val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].composeFunctor[G] } } private[data] sealed abstract class NestedInstances12 { - implicit def catsDataCommutativeApplicativeForNestedContravariant[F[_]: CommutativeApplicative, G[_]: CommutativeApplicative]: CommutativeApplicative[Nested[F, G, ?]] = - new NestedApplicative[F, G] with CommutativeApplicative[Nested[F, G, ?]] { - val FG: Applicative[λ[α => F[G[α]]]] = Applicative[F].compose[G] + implicit def catsDataInvariantForNestedContravariant[F[_]: Invariant, G[_]: Contravariant]: Invariant[Nested[F, G, ?]] = + new NestedInvariant[F, G] { + val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].composeContravariant[G] } } From 7f1f6c799c84af1dfeed680ab0f7f881ef28ed5a Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Mon, 2 Oct 2017 19:29:21 +0200 Subject: [PATCH 08/27] Fix priority for Tuple2K instances --- core/src/main/scala/cats/data/Tuple2K.scala | 44 ++++++++++----------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/core/src/main/scala/cats/data/Tuple2K.scala b/core/src/main/scala/cats/data/Tuple2K.scala index 633c003083..d857c3d0d6 100644 --- a/core/src/main/scala/cats/data/Tuple2K.scala +++ b/core/src/main/scala/cats/data/Tuple2K.scala @@ -61,10 +61,11 @@ private[data] sealed abstract class Tuple2KInstances2 extends Tuple2KInstances3 } private[data] sealed abstract class Tuple2KInstances3 extends Tuple2KInstances4 { - implicit def catsDataApplicativeForTuple2K[F[_], G[_]](implicit FF: Applicative[F], GG: Applicative[G]): Applicative[λ[α => Tuple2K[F, G, α]]] = new Tuple2KApplicative[F, G] { - def F: Applicative[F] = FF - def G: Applicative[G] = GG - } + implicit def catsDataCommutativeApplicativeForTuple2K[F[_], G[_]](implicit FF: CommutativeApplicative[F], GG: CommutativeApplicative[G]): CommutativeApplicative[λ[α => Tuple2K[F, G, α]]] = + new Tuple2KApplicative[F, G] with CommutativeApplicative[λ[α => Tuple2K[F, G, α]]] { + def F: Applicative[F] = FF + def G: Applicative[G] = GG + } } private[data] sealed abstract class Tuple2KInstances4 extends Tuple2KInstances5 { @@ -72,35 +73,34 @@ private[data] sealed abstract class Tuple2KInstances4 extends Tuple2KInstances5 def F: SemigroupK[F] = FF def G: SemigroupK[G] = GG } - implicit def catsDataApplyForTuple2K[F[_], G[_]](implicit FF: Apply[F], GG: Apply[G]): Apply[λ[α => Tuple2K[F, G, α]]] = new Tuple2KApply[F, G] { - def F: Apply[F] = FF - def G: Apply[G] = GG - } + implicit def catsDataCommutativeApplyForTuple2K[F[_], G[_]](implicit FF: CommutativeApply[F], GG: CommutativeApply[G]): CommutativeApply[λ[α => Tuple2K[F, G, α]]] = + new Tuple2KApply[F, G] with CommutativeApply[λ[α => Tuple2K[F, G, α]]] { + def F: Apply[F] = FF + def G: Apply[G] = GG + } } private[data] sealed abstract class Tuple2KInstances5 extends Tuple2KInstances6 { - implicit def catsDataFunctorForTuple2K[F[_], G[_]](implicit FF: Functor[F], GG: Functor[G]): Functor[λ[α => Tuple2K[F, G, α]]] = new Tuple2KFunctor[F, G] { - def F: Functor[F] = FF - def G: Functor[G] = GG + implicit def catsDataApplicativeForTuple2K[F[_], G[_]](implicit FF: Applicative[F], GG: Applicative[G]): Applicative[λ[α => Tuple2K[F, G, α]]] = new Tuple2KApplicative[F, G] { + def F: Applicative[F] = FF + def G: Applicative[G] = GG } } private[data] sealed abstract class Tuple2KInstances6 extends Tuple2KInstances7 { - - implicit def catsDataCommutativeApplyForTuple2K[F[_], G[_]](implicit FF: CommutativeApply[F], GG: CommutativeApply[G]): CommutativeApply[λ[α => Tuple2K[F, G, α]]] = - new Tuple2KApply[F, G] with CommutativeApply[λ[α => Tuple2K[F, G, α]]] { - def F: Apply[F] = FF - def G: Apply[G] = GG - } + implicit def catsDataApplyForTuple2K[F[_], G[_]](implicit FF: Apply[F], GG: Apply[G]): Apply[λ[α => Tuple2K[F, G, α]]] = new Tuple2KApply[F, G] { + def F: Apply[F] = FF + def G: Apply[G] = GG + } } private[data] sealed abstract class Tuple2KInstances7 { - implicit def catsDataCommutativeApplicativeForTuple2K[F[_], G[_]](implicit FF: CommutativeApplicative[F], GG: CommutativeApplicative[G]): CommutativeApplicative[λ[α => Tuple2K[F, G, α]]] = - new Tuple2KApplicative[F, G] with CommutativeApplicative[λ[α => Tuple2K[F, G, α]]] { - def F: Applicative[F] = FF - def G: Applicative[G] = GG - } + + implicit def catsDataFunctorForTuple2K[F[_], G[_]](implicit FF: Functor[F], GG: Functor[G]): Functor[λ[α => Tuple2K[F, G, α]]] = new Tuple2KFunctor[F, G] { + def F: Functor[F] = FF + def G: Functor[G] = GG + } } private[data] sealed trait Tuple2KFunctor[F[_], G[_]] extends Functor[λ[α => Tuple2K[F, G, α]]] { From fa8a7319cf6b6fc05de43e0bba16820dd1158a33 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sat, 14 Oct 2017 11:15:19 -0400 Subject: [PATCH 09/27] Move methods to typeclass --- .../scala/cats/CommutativeApplicative.scala | 31 ++++++++----------- .../src/test/scala/cats/tests/MapTests.scala | 12 +++---- .../src/test/scala/cats/tests/SetTests.scala | 16 +++++----- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/core/src/main/scala/cats/CommutativeApplicative.scala b/core/src/main/scala/cats/CommutativeApplicative.scala index d524388784..8a5f86fdd5 100644 --- a/core/src/main/scala/cats/CommutativeApplicative.scala +++ b/core/src/main/scala/cats/CommutativeApplicative.scala @@ -11,31 +11,26 @@ import simulacrum.typeclass * * Must obey the laws defined in cats.laws.CommutativeApplicativeLaws. */ -@typeclass trait CommutativeApplicative[F[_]] extends Applicative[F] with CommutativeApply[F] - - -object CommutativeApplicative { - - def traverseUnordered[F[_]: CommutativeApplicative, A, B](sa: Set[A])(f: A => F[B]): F[Set[B]] = - sa.foldLeft(Applicative[F].pure(Set.empty[B])) { (acc, a) => - Applicative[F].map2(acc, f(a))(_ + _) +@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[F[_]: CommutativeApplicative, A](sa: Set[F[A]]): F[Set[A]] = - sa.foldLeft(Applicative[F].pure(Set.empty[A])) { (acc, a) => - Applicative[F].map2(acc, a)(_ + _) + def sequenceUnordered[A](sa: Set[F[A]]): F[Set[A]] = + sa.foldLeft(pure(Set.empty[A])) { (acc, a) => + map2(acc, a)(_ + _) } - def traverseUnorderedMap[F[_]: CommutativeApplicative, K, L, A, B](sa: Map[K, A])(f: (K, A) => F[(L, B)]): F[Map[L, B]] = - sa.foldLeft(Applicative[F].pure(Map.empty[L, B])) { (acc, a) => - Applicative[F].map2(acc, f.tupled(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[F[_]: CommutativeApplicative, K, L, A](sa: Map[K, F[(K, A)]]): F[Map[K, A]] = { - sa.foldLeft(Applicative[F].pure(Map.empty[K, A])) { (acc, a) => - Applicative[F].map2(acc, a._2)(_ + _) + 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)(_ + _) } } - } diff --git a/tests/src/test/scala/cats/tests/MapTests.scala b/tests/src/test/scala/cats/tests/MapTests.scala index d1f2c80668..6a3c955ce5 100644 --- a/tests/src/test/scala/cats/tests/MapTests.scala +++ b/tests/src/test/scala/cats/tests/MapTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.Tuple2K +import cats.data.{Tuple2K, Validated} import cats.laws.discipline.{TraverseTests, FlatMapTests, SerializableTests, SemigroupalTests} class MapTests extends CatsSuite { @@ -18,23 +18,23 @@ class MapTests extends CatsSuite { test("traverseUnordered identity") { forAll { (mi: Map[Int, Int], f: (Int, Int) => (String, String)) => - CommutativeApplicative.traverseUnorderedMap[Id, Int, String, Int, String](mi)(f) should === (mi.map(f.tupled)) + 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.traverseUnorderedMap(si)((i, j) => Tuple2K(f(i, j), g(i, j))) - val rhs = Tuple2K(CommutativeApplicative.traverseUnorderedMap(si)(f), CommutativeApplicative.traverseUnorderedMap(si)(g)) + 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.traverseUnorderedMap(mi)((i,j) => f(i, j).valid[Int]) should - === (CommutativeApplicative.sequenceUnorderedMap(mi.map(i => (f.tupled(i)._1, f.tupled(i).valid[Int])))) + 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])))) } } diff --git a/tests/src/test/scala/cats/tests/SetTests.scala b/tests/src/test/scala/cats/tests/SetTests.scala index 44bd9680e8..71d96540af 100644 --- a/tests/src/test/scala/cats/tests/SetTests.scala +++ b/tests/src/test/scala/cats/tests/SetTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.data.{Nested, Tuple2K} +import cats.data.{Nested, Tuple2K, Validated} import cats.laws.discipline.{FoldableTests, MonoidKTests, SerializableTests} import cats.kernel.laws.discipline.{MonoidLawTests} class SetTests extends CatsSuite { @@ -24,30 +24,30 @@ class SetTests extends CatsSuite { test("traverseUnordered identity") { forAll { (si: Set[Int], f: Int => String) => - CommutativeApplicative.traverseUnordered[Id, Int, String](si)(f) should === (si.map(f)) + 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.traverseUnordered(si)(f).map(ss => CommutativeApplicative.traverseUnordered(ss)(g))) - val rhs = CommutativeApplicative.traverseUnordered(si)(i => Nested(f(i).map(g))) + 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.traverseUnordered(si)(i => Tuple2K(f(i), g(i))) - val rhs = Tuple2K(CommutativeApplicative.traverseUnordered(si)(f), CommutativeApplicative.traverseUnordered(si)(g)) + 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.traverseUnordered(si)(i => f(i).valid[Int]) should - === (CommutativeApplicative.sequenceUnordered(si.map(i => f(i).valid[Int]))) + CommutativeApplicative[Validated[Int, ?]].traverseUnordered(si)(i => f(i).valid[Int]) should + === (CommutativeApplicative[Validated[Int, ?]].sequenceUnordered(si.map(i => f(i).valid[Int]))) } } From 2aeddd766237258c81cc541aeb8620f29c34d6ba Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 17 Oct 2017 10:23:30 -0400 Subject: [PATCH 10/27] Deduplicate Applicative instance --- core/src/main/scala/cats/data/Validated.scala | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index f67785939b..ceb3927dda 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -345,19 +345,7 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance } implicit def catsDataApplicativeErrorForValidated[E](implicit E: Semigroup[E]): ApplicativeError[Validated[E, ?], E] = - new ApplicativeError[Validated[E, ?], E] { - - def pure[A](a: A): Validated[E, A] = - Validated.valid(a) - - override def map[A, B](fa: Validated[E, A])(f: A => B): Validated[E, B] = - fa.map(f) - - def ap[A, B](f: Validated[E, A => B])(fa: Validated[E, A]): Validated[E, B] = - fa.ap(f)(E) - - override def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = - fa.product(fb)(E) + new ValidatedApplicative[E] with ApplicativeError[Validated[E, ?], E] { def handleErrorWith[A](fa: Validated[E, A])(f: E => Validated[E, A]): Validated[E, A] = fa match { @@ -376,18 +364,7 @@ private[data] sealed abstract class ValidatedInstances1 extends ValidatedInstanc } implicit def catsDataCommutativeApplicativeForValidated[E: CommutativeSemigroup]: CommutativeApplicative[Validated[E, ?]] = - new CommutativeApplicative[Validated[E, ?]] { - override def map[A, B](fa: Validated[E, A])(f: A => B): Validated[E, B] = - fa.map(f) - - def pure[A](a: A): Validated[E, A] = Validated.valid(a) - - def ap[A, B](ff: Validated[E, (A) => B])(fa: Validated[E, A]): Validated[E, B] = - fa.ap(ff)(Semigroup[E]) - - override def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = - fa.product(fb)(Semigroup[E]) - } + new ValidatedApplicative[E] with CommutativeApplicative[Validated[E, ?]] implicit def catsDataPartialOrderForValidated[A: PartialOrder, B: PartialOrder]: PartialOrder[Validated[A, B]] = new PartialOrder[Validated[A, B]] { @@ -456,6 +433,19 @@ private[data] sealed abstract class ValidatedInstances2 { // scalastyle:off method.length } +private[data] class ValidatedApplicative[E: Semigroup] extends CommutativeApplicative[Validated[E, ?]] { + override def map[A, B](fa: Validated[E, A])(f: A => B): Validated[E, B] = + fa.map(f) + + def pure[A](a: A): Validated[E, A] = Validated.valid(a) + + def ap[A, B](ff: Validated[E, (A) => B])(fa: Validated[E, A]): Validated[E, B] = + fa.ap(ff)(Semigroup[E]) + + override def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = + fa.product(fb)(Semigroup[E]) +} + private[data] trait ValidatedFunctions { /** * Converts an `A` to a `Validated[A, B]`. From aa49de95ffc107fc023bdb430296551929ae8506 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 17 Oct 2017 16:43:40 -0400 Subject: [PATCH 11/27] Try out new typeclasses --- .../scala/cats/CommutativeApplicative.scala | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/core/src/main/scala/cats/CommutativeApplicative.scala b/core/src/main/scala/cats/CommutativeApplicative.scala index 8a5f86fdd5..2bd13b5cd8 100644 --- a/core/src/main/scala/cats/CommutativeApplicative.scala +++ b/core/src/main/scala/cats/CommutativeApplicative.scala @@ -34,3 +34,33 @@ import simulacrum.typeclass } } } + +@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 +} From d308cf7ef105ee95110ffb74f4fbb77f40da9c1f Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 18 Oct 2017 18:49:17 -0400 Subject: [PATCH 12/27] 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") From 0ffbc55a43d2c64dcaad449289ef060b237f51ad Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 18 Oct 2017 21:31:55 -0400 Subject: [PATCH 13/27] Remove Set from Foldable docs --- docs/src/main/tut/typeclasses/foldable.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/main/tut/typeclasses/foldable.md b/docs/src/main/tut/typeclasses/foldable.md index 4e73e33f39..d662ae1c58 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. @@ -40,10 +40,10 @@ Foldable[List].reduceLeftToOption(List[Int]())(_.toString)((s,i) => s + i) Foldable[List].reduceLeftToOption(List(1,2,3,4))(_.toString)((s,i) => s + i) Foldable[List].reduceRightToOption(List(1,2,3,4))(_.toString)((i,s) => Later(s.value + i)).value Foldable[List].reduceRightToOption(List[Int]())(_.toString)((i,s) => Later(s.value + i)).value -Foldable[Set].find(Set(1,2,3))(_ > 2) -Foldable[Set].exists(Set(1,2,3))(_ > 2) -Foldable[Set].forall(Set(1,2,3))(_ > 2) -Foldable[Set].forall(Set(1,2,3))(_ < 4) +Foldable[Vector].find(Vector(1,2,3))(_ > 2) +Foldable[Vector].exists(Vector(1,2,3))(_ > 2) +Foldable[Vector].forall(Vector(1,2,3))(_ > 2) +Foldable[Vector].forall(Vector(1,2,3))(_ < 4) Foldable[Vector].filter_(Vector(1,2,3))(_ < 3) Foldable[List].isEmpty(List(1,2)) Foldable[Option].isEmpty(None) From 23cb589ef703993c62e3eea4e7d0070a95cef2ad Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 19 Oct 2017 09:14:10 -0400 Subject: [PATCH 14/27] Add extra ref law --- .../cats/laws/UnorderedFoldableLaws.scala | 18 ++++++------------ .../discipline/UnorderedFoldableTests.scala | 4 +++- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala index c09fc7abc8..026278d2bf 100644 --- a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -17,17 +17,11 @@ trait UnorderedFoldableLaws[F[_]] { 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 = { + 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 = { + def forallConsistentWithExists[A](fa: F[A], p: A => Boolean): Boolean = { if (F.forall(fa)(p)) { val negationExists = F.exists(fa)(a => !(p(a))) @@ -43,10 +37,7 @@ trait UnorderedFoldableLaws[F[_]] { /** * If `F[A]` is empty, forall must return true. */ - def forallEmpty[A]( - fa: F[A], - p: A => Boolean - ): Boolean = { + def forallEmpty[A](fa: F[A], p: A => Boolean): Boolean = { !F.isEmpty(fa) || F.forall(fa)(p) } @@ -55,6 +46,9 @@ trait UnorderedFoldableLaws[F[_]] { buf += a }.toSet + def nonEmptyRef[A](fa: F[A]): IsEq[Boolean] = + F.nonEmpty(fa) <-> !F.isEmpty(fa) + } object UnorderedFoldableLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala index 4f12d6d995..65fee1261d 100644 --- a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala @@ -7,6 +7,7 @@ import Prop._ import org.typelevel.discipline.Laws import cats.kernel.CommutativeMonoid import cats.instances.set._ +import cats.instances.boolean._ trait UnorderedFoldableTests[F[_]] extends Laws { def laws: UnorderedFoldableLaws[F] @@ -29,7 +30,8 @@ trait UnorderedFoldableTests[F[_]] extends Laws { "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] _) + "toSet reference" -> forAll(laws.toSetRef[A] _), + "nonEmpty reference" -> forAll(laws.nonEmptyRef[A] _) ) } From 376a5c651c3fdeb820c4c151c382a31ebd0a2e2e Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 19 Oct 2017 13:50:20 -0400 Subject: [PATCH 15/27] Revert "Add UnorderedFoldable and UnorderedTraverse and move traversal functions there" This reverts commit d308cf7ef105ee95110ffb74f4fbb77f40da9c1f. --- .../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 | 57 ---------------- .../cats/laws/UnorderedTraverseLaws.scala | 62 ----------------- .../discipline/UnorderedFoldableTests.scala | 41 ------------ .../discipline/UnorderedTraverseTests.scala | 46 ------------- .../test/scala/cats/tests/FoldableSuite.scala | 16 +++++ .../src/test/scala/cats/tests/MapSuite.scala | 28 +++++++- .../scala/cats/tests/RegressionSuite.scala | 5 ++ .../src/test/scala/cats/tests/SetSuite.scala | 4 +- 13 files changed, 140 insertions(+), 315 deletions(-) delete mode 100644 core/src/main/scala/cats/UnorderedFoldable.scala delete mode 100644 core/src/main/scala/cats/UnorderedTraverse.scala delete mode 100644 laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala delete mode 100644 laws/src/main/scala/cats/laws/UnorderedTraverseLaws.scala delete mode 100644 laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala delete 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 99ca3fcdfc..2bd13b5cd8 100644 --- a/core/src/main/scala/cats/CommutativeApplicative.scala +++ b/core/src/main/scala/cats/CommutativeApplicative.scala @@ -11,4 +11,56 @@ import simulacrum.typeclass * * Must obey the laws defined in cats.laws.CommutativeApplicativeLaws. */ -@typeclass trait CommutativeApplicative[F[_]] extends Applicative[F] with CommutativeApply[F] +@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 +} diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala deleted file mode 100644 index a6412983f2..0000000000 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ /dev/null @@ -1,66 +0,0 @@ -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 deleted file mode 100644 index e452a23878..0000000000 --- a/core/src/main/scala/cats/UnorderedTraverse.scala +++ /dev/null @@ -1,14 +0,0 @@ -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 8814021d36..c364f6d60f 100644 --- a/core/src/main/scala/cats/instances/map.scala +++ b/core/src/main/scala/cats/instances/map.scala @@ -1,8 +1,6 @@ package cats package instances -import cats.kernel.CommutativeMonoid - import scala.annotation.tailrec trait MapInstances extends cats.kernel.instances.MapInstances { @@ -16,10 +14,10 @@ trait MapInstances extends cats.kernel.instances.MapInstances { } // scalastyle:off method.length - implicit def catsStdInstancesForMap[K]: UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] = - new UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] { + implicit def catsStdInstancesForMap[K]: Traverse[Map[K, ?]] with FlatMap[Map[K, ?]] = + new Traverse[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]] = { + def traverse[G[_], A, B](fa: Map[K, A])(f: A => G[B])(implicit G: Applicative[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)}) @@ -74,13 +72,22 @@ 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 unorderedFold[A](fa: Map[K, A])(implicit A: CommutativeMonoid[A]): A = + override def fold[A](fa: Map[K, A])(implicit A: Monoid[A]): A = A.combineAll(fa.values) - override def toSet[A](fa: Map[K, A]): Set[A] = fa.values.toSet + override def toList[A](fa: Map[K, A]): List[A] = fa.values.toList } // 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 770fb7a139..8cb380aa4c 100644 --- a/core/src/main/scala/cats/instances/set.scala +++ b/core/src/main/scala/cats/instances/set.scala @@ -1,24 +1,14 @@ package cats package instances -import cats.kernel.CommutativeMonoid +import scala.annotation.tailrec import cats.syntax.show._ trait SetInstances extends cats.kernel.instances.SetInstances { - 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)(_ + _) - } + implicit val catsStdInstancesForSet: Foldable[Set] with MonoidK[Set] = + new Foldable[Set] with MonoidK[Set] { def empty[A]: Set[A] = Set.empty[A] @@ -30,17 +20,36 @@ 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) - def insert[A](fa: Set[A], a: A): Set[A] = fa + a + 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 + } - override def unorderedFold[A](fa: Set[A])(implicit A: CommutativeMonoid[A]): A = A.combineAll(fa) + override def size[A](fa: Set[A]): Long = fa.size.toLong - override def toSet[A](fa: Set[A]): Set[A] = fa + override def exists[A](fa: Set[A])(p: A => Boolean): Boolean = + fa.exists(p) 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 deleted file mode 100644 index 026278d2bf..0000000000 --- a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala +++ /dev/null @@ -1,57 +0,0 @@ -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 - - 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 deleted file mode 100644 index 0841f4255b..0000000000 --- a/laws/src/main/scala/cats/laws/UnorderedTraverseLaws.scala +++ /dev/null @@ -1,62 +0,0 @@ -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 deleted file mode 100644 index 65fee1261d..0000000000 --- a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala +++ /dev/null @@ -1,41 +0,0 @@ -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._ -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, - "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] _), - "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 deleted file mode 100644 index 161354fabf..0000000000 --- a/laws/src/main/scala/cats/laws/discipline/UnorderedTraverseTests.scala +++ /dev/null @@ -1,46 +0,0 @@ -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/FoldableSuite.scala b/tests/src/test/scala/cats/tests/FoldableSuite.scala index 7447c7c922..ca1d55c477 100644 --- a/tests/src/test/scala/cats/tests/FoldableSuite.scala +++ b/tests/src/test/scala/cats/tests/FoldableSuite.scala @@ -206,6 +206,14 @@ class FoldableSuiteAdditional 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)) } @@ -303,10 +311,18 @@ class FoldableVectorSuite extends FoldableSuite[Vector]("vector") { def iterator[T](vector: Vector[T]): Iterator[T] = vector.iterator } +class FoldableSetSuite extends FoldableSuite[Set]("set") { + def iterator[T](set: Set[T]): Iterator[T] = set.iterator +} + class FoldableStreamSuite extends FoldableSuite[Stream]("stream") { def iterator[T](stream: Stream[T]): Iterator[T] = stream.iterator } +class FoldableMapSuite extends FoldableSuite[Map[Int, ?]]("map") { + def iterator[T](map: Map[Int, T]): Iterator[T] = map.valuesIterator +} + class FoldableOptionSuite extends FoldableSuite[Option]("option") { def iterator[T](option: Option[T]): Iterator[T] = option.iterator } diff --git a/tests/src/test/scala/cats/tests/MapSuite.scala b/tests/src/test/scala/cats/tests/MapSuite.scala index 9be630d7b5..32c99befce 100644 --- a/tests/src/test/scala/cats/tests/MapSuite.scala +++ b/tests/src/test/scala/cats/tests/MapSuite.scala @@ -1,7 +1,8 @@ package cats package tests -import cats.laws.discipline.{FlatMapTests, SemigroupalTests, SerializableTests, UnorderedTraverseTests} +import cats.data.{Tuple2K, Validated} +import cats.laws.discipline.{TraverseTests, FlatMapTests, SerializableTests, SemigroupalTests} class MapSuite extends CatsSuite { implicit val iso = SemigroupalTests.Isomorphisms.invariant[Map[Int, ?]] @@ -12,9 +13,30 @@ 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, ?]])) + 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, ?]])) + 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/RegressionSuite.scala b/tests/src/test/scala/cats/tests/RegressionSuite.scala index f6bf75ee03..e62c5f9df3 100644 --- a/tests/src/test/scala/cats/tests/RegressionSuite.scala +++ b/tests/src/test/scala/cats/tests/RegressionSuite.scala @@ -102,6 +102,11 @@ class RegressionSuite 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/SetSuite.scala b/tests/src/test/scala/cats/tests/SetSuite.scala index a83764f2ed..9d57d072de 100644 --- a/tests/src/test/scala/cats/tests/SetSuite.scala +++ b/tests/src/test/scala/cats/tests/SetSuite.scala @@ -12,8 +12,8 @@ class SetSuite extends CatsSuite { 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])) + checkAll("Set[Int]", FoldableTests[Set].foldable[Int, Int]) + checkAll("Foldable[Set]", SerializableTests.serializable(Foldable[Set])) test("show"){ Set(1, 1, 2, 3).show should === ("Set(1, 2, 3)") From d673655d57c319118282f47fff03ff770553981c Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 19 Oct 2017 13:50:41 -0400 Subject: [PATCH 16/27] Revert "Remove Set from Foldable docs" This reverts commit 0ffbc55a43d2c64dcaad449289ef060b237f51ad. --- .../scala/cats/CommutativeApplicative.scala | 54 +------------------ docs/src/main/tut/typeclasses/foldable.md | 10 ++-- .../src/test/scala/cats/tests/MapSuite.scala | 23 -------- .../src/test/scala/cats/tests/SetSuite.scala | 5 +- 4 files changed, 7 insertions(+), 85 deletions(-) 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/docs/src/main/tut/typeclasses/foldable.md b/docs/src/main/tut/typeclasses/foldable.md index d662ae1c58..4e73e33f39 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 `Vector`), these methods will fold +In the case of a collection (such as `List` or `Set`), 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. @@ -40,10 +40,10 @@ Foldable[List].reduceLeftToOption(List[Int]())(_.toString)((s,i) => s + i) Foldable[List].reduceLeftToOption(List(1,2,3,4))(_.toString)((s,i) => s + i) Foldable[List].reduceRightToOption(List(1,2,3,4))(_.toString)((i,s) => Later(s.value + i)).value Foldable[List].reduceRightToOption(List[Int]())(_.toString)((i,s) => Later(s.value + i)).value -Foldable[Vector].find(Vector(1,2,3))(_ > 2) -Foldable[Vector].exists(Vector(1,2,3))(_ > 2) -Foldable[Vector].forall(Vector(1,2,3))(_ > 2) -Foldable[Vector].forall(Vector(1,2,3))(_ < 4) +Foldable[Set].find(Set(1,2,3))(_ > 2) +Foldable[Set].exists(Set(1,2,3))(_ > 2) +Foldable[Set].forall(Set(1,2,3))(_ > 2) +Foldable[Set].forall(Set(1,2,3))(_ < 4) Foldable[Vector].filter_(Vector(1,2,3))(_ < 3) Foldable[List].isEmpty(List(1,2)) Foldable[Option].isEmpty(None) diff --git a/tests/src/test/scala/cats/tests/MapSuite.scala b/tests/src/test/scala/cats/tests/MapSuite.scala index 32c99befce..3287a429a6 100644 --- a/tests/src/test/scala/cats/tests/MapSuite.scala +++ b/tests/src/test/scala/cats/tests/MapSuite.scala @@ -1,7 +1,6 @@ package cats package tests -import cats.data.{Tuple2K, Validated} import cats.laws.discipline.{TraverseTests, FlatMapTests, SerializableTests, SemigroupalTests} class MapSuite extends CatsSuite { @@ -16,28 +15,6 @@ class MapSuite extends CatsSuite { 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, ?]])) - 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]) => map.show.nonEmpty should === (true) diff --git a/tests/src/test/scala/cats/tests/SetSuite.scala b/tests/src/test/scala/cats/tests/SetSuite.scala index 9d57d072de..a01f7562b2 100644 --- a/tests/src/test/scala/cats/tests/SetSuite.scala +++ b/tests/src/test/scala/cats/tests/SetSuite.scala @@ -1,11 +1,8 @@ package cats package tests +import cats.laws.discipline.{FoldableTests, 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) From 8666c6415e1183a00d46e62844b9ff22aae15b28 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 19 Oct 2017 14:03:18 -0400 Subject: [PATCH 17/27] Add Unordered type classes --- .../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 +++++------- docs/src/main/tut/typeclasses/foldable.md | 10 +-- .../cats/laws/UnorderedFoldableLaws.scala | 57 ++++++++++++++++ .../cats/laws/UnorderedTraverseLaws.scala | 62 +++++++++++++++++ .../discipline/UnorderedFoldableTests.scala | 41 ++++++++++++ .../discipline/UnorderedTraverseTests.scala | 46 +++++++++++++ .../test/scala/cats/tests/FoldableSuite.scala | 16 ----- .../src/test/scala/cats/tests/MapSuite.scala | 7 +- .../scala/cats/tests/RegressionSuite.scala | 5 -- .../src/test/scala/cats/tests/SetSuite.scala | 9 ++- 13 files changed, 324 insertions(+), 71 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/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/docs/src/main/tut/typeclasses/foldable.md b/docs/src/main/tut/typeclasses/foldable.md index 4e73e33f39..d662ae1c58 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. @@ -40,10 +40,10 @@ Foldable[List].reduceLeftToOption(List[Int]())(_.toString)((s,i) => s + i) Foldable[List].reduceLeftToOption(List(1,2,3,4))(_.toString)((s,i) => s + i) Foldable[List].reduceRightToOption(List(1,2,3,4))(_.toString)((i,s) => Later(s.value + i)).value Foldable[List].reduceRightToOption(List[Int]())(_.toString)((i,s) => Later(s.value + i)).value -Foldable[Set].find(Set(1,2,3))(_ > 2) -Foldable[Set].exists(Set(1,2,3))(_ > 2) -Foldable[Set].forall(Set(1,2,3))(_ > 2) -Foldable[Set].forall(Set(1,2,3))(_ < 4) +Foldable[Vector].find(Vector(1,2,3))(_ > 2) +Foldable[Vector].exists(Vector(1,2,3))(_ > 2) +Foldable[Vector].forall(Vector(1,2,3))(_ > 2) +Foldable[Vector].forall(Vector(1,2,3))(_ < 4) Foldable[Vector].filter_(Vector(1,2,3))(_ < 3) Foldable[List].isEmpty(List(1,2)) Foldable[Option].isEmpty(None) 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..026278d2bf --- /dev/null +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -0,0 +1,57 @@ +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 + + 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/UnorderedFoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala new file mode 100644 index 0000000000..65fee1261d --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala @@ -0,0 +1,41 @@ +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._ +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, + "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] _), + "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/FoldableSuite.scala b/tests/src/test/scala/cats/tests/FoldableSuite.scala index ca1d55c477..7447c7c922 100644 --- a/tests/src/test/scala/cats/tests/FoldableSuite.scala +++ b/tests/src/test/scala/cats/tests/FoldableSuite.scala @@ -206,14 +206,6 @@ class FoldableSuiteAdditional 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 FoldableVectorSuite extends FoldableSuite[Vector]("vector") { def iterator[T](vector: Vector[T]): Iterator[T] = vector.iterator } -class FoldableSetSuite extends FoldableSuite[Set]("set") { - def iterator[T](set: Set[T]): Iterator[T] = set.iterator -} - class FoldableStreamSuite extends FoldableSuite[Stream]("stream") { def iterator[T](stream: Stream[T]): Iterator[T] = stream.iterator } -class FoldableMapSuite extends FoldableSuite[Map[Int, ?]]("map") { - def iterator[T](map: Map[Int, T]): Iterator[T] = map.valuesIterator -} - class FoldableOptionSuite extends FoldableSuite[Option]("option") { def iterator[T](option: Option[T]): Iterator[T] = option.iterator } diff --git a/tests/src/test/scala/cats/tests/MapSuite.scala b/tests/src/test/scala/cats/tests/MapSuite.scala index 3287a429a6..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.{TraverseTests, FlatMapTests, SerializableTests, SemigroupalTests} +import cats.laws.discipline.{FlatMapTests, SemigroupalTests, SerializableTests, UnorderedTraverseTests} class MapSuite extends CatsSuite { implicit val iso = SemigroupalTests.Isomorphisms.invariant[Map[Int, ?]] @@ -12,8 +12,9 @@ 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", 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("show isn't empty and is formatted as expected") { forAll { (map: Map[Int, String]) => diff --git a/tests/src/test/scala/cats/tests/RegressionSuite.scala b/tests/src/test/scala/cats/tests/RegressionSuite.scala index e62c5f9df3..f6bf75ee03 100644 --- a/tests/src/test/scala/cats/tests/RegressionSuite.scala +++ b/tests/src/test/scala/cats/tests/RegressionSuite.scala @@ -102,11 +102,6 @@ class RegressionSuite 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/SetSuite.scala b/tests/src/test/scala/cats/tests/SetSuite.scala index a01f7562b2..a83764f2ed 100644 --- a/tests/src/test/scala/cats/tests/SetSuite.scala +++ b/tests/src/test/scala/cats/tests/SetSuite.scala @@ -1,16 +1,19 @@ package cats package tests -import cats.laws.discipline.{FoldableTests, 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]", 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)") From 74b022892cc8fa92d15bfd9601b06fddee6245a6 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 19 Oct 2017 16:01:24 -0400 Subject: [PATCH 18/27] Make UnorderedFoldable and Traverse super classes --- core/src/main/scala/cats/Foldable.scala | 22 +++++++------ core/src/main/scala/cats/Traverse.scala | 8 ++++- .../src/main/scala/cats/syntax/foldable.scala | 2 +- .../main/scala/cats/laws/FoldableLaws.scala | 33 +------------------ .../main/scala/cats/laws/TraverseLaws.scala | 2 +- .../cats/laws/discipline/FoldableTests.scala | 14 +++----- .../discipline/NonEmptyTraverseTests.scala | 20 ++++++++--- .../cats/laws/discipline/ReducibleTests.scala | 6 ++-- .../cats/laws/discipline/TraverseTests.scala | 18 ++++++---- .../src/test/scala/cats/tests/ListSuite.scala | 2 +- .../test/scala/cats/tests/NestedSuite.scala | 2 +- .../test/scala/cats/tests/QueueSuite.scala | 2 +- .../test/scala/cats/tests/StreamSuite.scala | 2 +- .../test/scala/cats/tests/VectorSuite.scala | 2 +- 14 files changed, 62 insertions(+), 73 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 16749333d0..5a5364170a 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -3,6 +3,7 @@ package cats import scala.collection.mutable import cats.instances.either._ import cats.instances.long._ +import cats.kernel.CommutativeMonoid import simulacrum.typeclass /** @@ -24,12 +25,8 @@ import simulacrum.typeclass * * 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'. - */ - 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'. @@ -301,7 +298,7 @@ import simulacrum.typeclass /** * Find the first element matching the predicate, if one exists. */ - def find[A](fa: F[A])(f: A => Boolean): Option[A] = + override def find[A](fa: F[A])(f: A => Boolean): Option[A] = foldRight(fa, Now(Option.empty[A])) { (a, lb) => if (f(a)) Now(Some(a)) else lb }.value @@ -311,7 +308,7 @@ import simulacrum.typeclass * * 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 @@ -321,7 +318,7 @@ import simulacrum.typeclass * * 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 @@ -460,10 +457,10 @@ import simulacrum.typeclass /** * 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) /** @@ -502,6 +499,11 @@ import simulacrum.typeclass 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/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 87902c3ca3..8c686be4c8 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/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 8adc63040b..cad1900c60 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,12 +26,6 @@ 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 = { - F.exists(fa)(p) == F.find(fa)(p).isDefined - } def existsLazy[A](fa: F[A]): Boolean = { var i = 0 @@ -51,31 +45,6 @@ trait FoldableLaws[F[_]] { 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 e7929fe4bf..3bcfc29452 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/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index d7a3868bec..6ba4ff7fa4 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -2,19 +2,18 @@ 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._ -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], @@ -23,12 +22,9 @@ 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] _), - "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 df9edce95b..7a0578490a 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 @@ -22,8 +22,8 @@ trait ReducibleTests[F[_]] extends FoldableTests[F] { EqA: Eq[A], EqB: Eq[B], 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 7cb5b1be4f..a6559cd35a 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/tests/src/test/scala/cats/tests/ListSuite.scala b/tests/src/test/scala/cats/tests/ListSuite.scala index 8f881438f2..664426a5bb 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])) test("nel => list => nel returns original nel")( 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/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/StreamSuite.scala b/tests/src/test/scala/cats/tests/StreamSuite.scala index 648f114f6e..a200cd6dba 100644 --- a/tests/src/test/scala/cats/tests/StreamSuite.scala +++ b/tests/src/test/scala/cats/tests/StreamSuite.scala @@ -16,7 +16,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])) test("show") { diff --git a/tests/src/test/scala/cats/tests/VectorSuite.scala b/tests/src/test/scala/cats/tests/VectorSuite.scala index 9c9137cfd5..18aad58e86 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])) test("show") { From dfa8bd34766471b0013f57f9eb8f238a0badd0e8 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 20 Oct 2017 14:41:18 -0400 Subject: [PATCH 19/27] Make unorderedFoldMap the main method --- core/src/main/scala/cats/Foldable.scala | 7 ++- .../main/scala/cats/UnorderedFoldable.scala | 50 ++++++++----------- core/src/main/scala/cats/instances/map.scala | 7 +-- core/src/main/scala/cats/instances/set.scala | 11 +--- .../main/scala/cats/laws/FoldableLaws.scala | 4 ++ .../cats/laws/UnorderedFoldableLaws.scala | 15 +----- .../cats/laws/discipline/FoldableTests.scala | 1 + .../discipline/UnorderedFoldableTests.scala | 2 - 8 files changed, 38 insertions(+), 59 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 5a5364170a..6988964770 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -27,6 +27,11 @@ import simulacrum.typeclass */ @typeclass trait Foldable[F[_]] extends UnorderedFoldable[F] { self => + /** + * Left associative fold on 'F' using the function 'f'. + */ + 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'. @@ -298,7 +303,7 @@ import simulacrum.typeclass /** * Find the first element matching the predicate, if one exists. */ - override def find[A](fa: F[A])(f: A => Boolean): Option[A] = + def find[A](fa: F[A])(f: A => Boolean): Option[A] = foldRight(fa, Now(Option.empty[A])) { (a, lb) => if (f(a)) Now(Some(a)) else lb }.value diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index a6412983f2..18e64ebc44 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -2,57 +2,36 @@ package cats import cats.kernel.CommutativeMonoid import simulacrum.typeclass - -import scala.collection.mutable - +import cats.instances.set._ /** * `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 unorderedFoldMap[A, B: CommutativeMonoid](fa: F[A])(f: A => B): B 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 - + unorderedFoldMap(fa)(a => Set(a)) /** * Returns true if there are no elements. Otherwise false. */ def isEmpty[A](fa: F[A]): Boolean = - foldLeft(fa, true)((_, _) => false) + unorderedFoldMap(fa)(a => true)(UnorderedFoldable.orMonoid) 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 - } + unorderedFoldMap(fa)(p)(UnorderedFoldable.orMonoid) /** * Check whether all elements satisfy the predicate. @@ -60,7 +39,20 @@ import scala.collection.mutable * 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 - } + unorderedFoldMap(fa)(p)(UnorderedFoldable.andMonoid) +} + +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 + } + } diff --git a/core/src/main/scala/cats/instances/map.scala b/core/src/main/scala/cats/instances/map.scala index 8814021d36..374a1c4c2e 100644 --- a/core/src/main/scala/cats/instances/map.scala +++ b/core/src/main/scala/cats/instances/map.scala @@ -49,11 +49,8 @@ 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 foldLeft[A, B](fa: Map[K, A], b: B)(f: (B, A) => B): B = - fa.foldLeft(b) { case (x, (k, a)) => f(x, a)} - - def foldRight[A, B](fa: Map[K, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - Foldable.iterateRight(fa.values.iterator, lb)(f) + 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] diff --git a/core/src/main/scala/cats/instances/set.scala b/core/src/main/scala/cats/instances/set.scala index 770fb7a139..4eb973e513 100644 --- a/core/src/main/scala/cats/instances/set.scala +++ b/core/src/main/scala/cats/instances/set.scala @@ -24,13 +24,8 @@ trait SetInstances extends cats.kernel.instances.SetInstances { def combineK[A](x: Set[A], y: Set[A]): Set[A] = x | y - def foldLeft[A, B](fa: Set[A], b: B)(f: (B, A) => B): B = - fa.foldLeft(b)(f) - - def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = - Foldable.iterateRight(fa.iterator, lb)(f) - - def insert[A](fa: Set[A], a: A): Set[A] = fa + a + 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) @@ -40,8 +35,6 @@ trait SetInstances extends cats.kernel.instances.SetInstances { fa.forall(p) override def isEmpty[A](fa: Set[A]): Boolean = fa.isEmpty - - override def find[A](fa: Set[A])(f: A => Boolean): Option[A] = fa.find(f) } implicit def catsStdShowForSet[A:Show]: Show[Set[A]] = new Show[Set[A]] { diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index cad1900c60..272bb670b2 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -26,6 +26,10 @@ trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[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 = { + F.exists(fa)(p) == F.find(fa)(p).isDefined + } + def existsLazy[A](fa: F[A]): Boolean = { var i = 0 diff --git a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala index 026278d2bf..de9ae00aff 100644 --- a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -4,22 +4,13 @@ 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)) { @@ -42,9 +33,7 @@ trait UnorderedFoldableLaws[F[_]] { } def toSetRef[A](fa: F[A]): IsEq[Set[A]] = - F.toSet(fa) <-> F.foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) => - buf += a - }.toSet + F.unorderedFoldMap(fa)(a => Set(a)) <-> F.toSet(fa) def nonEmptyRef[A](fa: F[A]): IsEq[Boolean] = F.nonEmpty(fa) <-> !F.isEmpty(fa) diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index 6ba4ff7fa4..0152553b12 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -25,6 +25,7 @@ trait FoldableTests[F[_]] extends UnorderedFoldableTests[F] { parent = Some(unorderedFoldable[A, B]), "foldLeft consistent with foldMap" -> forAll(laws.leftFoldConsistentWithFoldMap[A, B] _), "foldRight consistent with foldMap" -> forAll(laws.rightFoldConsistentWithFoldMap[A, B] _), + "exists consistent with find" -> forAll(laws.existsConsistentWithFind[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/UnorderedFoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala index 65fee1261d..a094a245db 100644 --- a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala @@ -25,9 +25,7 @@ trait UnorderedFoldableTests[F[_]] extends Laws { 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] _), From 903694d6f9309dbdd1d00bac8b81b597985e8658 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 20 Oct 2017 17:56:21 -0400 Subject: [PATCH 20/27] define isEmpty in terms of exists --- core/src/main/scala/cats/UnorderedFoldable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index 18e64ebc44..737e5ca66c 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -20,7 +20,7 @@ import cats.instances.set._ * Returns true if there are no elements. Otherwise false. */ def isEmpty[A](fa: F[A]): Boolean = - unorderedFoldMap(fa)(a => true)(UnorderedFoldable.orMonoid) + exists(fa)(Function.const(true)) def nonEmpty[A](fa: F[A]): Boolean = !isEmpty(fa) From 5f4d41fb6998c68bdec0a611578d6f95f3c84b67 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 31 Oct 2017 12:58:22 +0100 Subject: [PATCH 21/27] Add mention to unorderedFoldable in foldable scaladoc --- core/src/main/scala/cats/Foldable.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 10ecec71e6..27cf6cc946 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -16,6 +16,7 @@ import simulacrum.typeclass * `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: * From 5d9f0f70f3b4dcae6790a19b557d9d23b53d5790 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 10 Nov 2017 12:03:42 +0100 Subject: [PATCH 22/27] Add size to UnorderedFoldable --- core/src/main/scala/cats/Foldable.scala | 10 ---------- core/src/main/scala/cats/UnorderedFoldable.scala | 10 ++++++++++ core/src/main/scala/cats/UnorderedTraverse.scala | 3 +-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 27cf6cc946..ad60ab86f4 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -147,16 +147,6 @@ import simulacrum.typeclass 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`. */ diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index 737e5ca66c..ac120063d3 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -40,6 +40,16 @@ import cats.instances.set._ */ def forall[A](fa: F[A])(p: A => Boolean): Boolean = unorderedFoldMap(fa)(p)(UnorderedFoldable.andMonoid) + + /** + * 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)(_ => 1) } object UnorderedFoldable { diff --git a/core/src/main/scala/cats/UnorderedTraverse.scala b/core/src/main/scala/cats/UnorderedTraverse.scala index e452a23878..3fab6f1124 100644 --- a/core/src/main/scala/cats/UnorderedTraverse.scala +++ b/core/src/main/scala/cats/UnorderedTraverse.scala @@ -3,8 +3,7 @@ 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`. + * `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]] From 04722f02ad603ce9eb15e610b69659086fbd706c Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 14 Nov 2017 12:50:56 +0100 Subject: [PATCH 23/27] Made exists and forall sufficiently lazy --- .../main/scala/cats/UnorderedFoldable.scala | 9 +++++++-- .../main/scala/cats/laws/FoldableLaws.scala | 19 ------------------- .../cats/laws/UnorderedFoldableLaws.scala | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index a03868980d..7701447b68 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -32,7 +32,8 @@ import cats.instances.long._ * If there are no elements, the result is `false`. */ def exists[A](fa: F[A])(p: A => Boolean): Boolean = - unorderedFoldMap(fa)(p)(UnorderedFoldable.orMonoid) + unorderedFoldMap(fa)(a => Eval.later(p(a)))(UnorderedFoldable.commutativeMonoidEval(UnorderedFoldable.orMonoid)) + .value /** * Check whether all elements satisfy the predicate. @@ -40,7 +41,8 @@ import cats.instances.long._ * If there are no elements, the result is `true`. */ def forall[A](fa: F[A])(p: A => Boolean): Boolean = - unorderedFoldMap(fa)(p)(UnorderedFoldable.andMonoid) + unorderedFoldMap(fa)(a => Eval.later(p(a)))(UnorderedFoldable.commutativeMonoidEval(UnorderedFoldable.andMonoid)) + .value /** * The size of this UnorderedFoldable. @@ -66,4 +68,7 @@ object UnorderedFoldable { 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/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index e0add60030..4fc06eaff2 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -31,25 +31,6 @@ trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[F] { } - 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) - } - - /** * Monadic folding with identity monad is analogous to `foldLeft`. */ diff --git a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala index de9ae00aff..d73a79b753 100644 --- a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -25,6 +25,24 @@ trait UnorderedFoldableLaws[F[_]] { } 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. */ From 9568933e2c7e9cec2dba2d0e61277c795b893e82 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 14 Nov 2017 13:22:44 +0100 Subject: [PATCH 24/27] Add Mima exceptions --- build.sbt | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 9ed50b92be..706c216106 100644 --- a/build.sbt +++ b/build.sbt @@ -190,10 +190,145 @@ lazy val binaryCompatibleVersion = "1.0.0-RC1" def mimaSettings(moduleName: String) = Seq( mimaPreviousArtifacts := Set("org.typelevel" %% moduleName % binaryCompatibleVersion), // TODO: remove this post-release of 1.0.0 - mimaBinaryIssueFilters += { + mimaBinaryIssueFilters ++= { import com.typesafe.tools.mima.core._ import com.typesafe.tools.mima.core.ProblemFilters._ - exclude[ReversedMissingMethodProblem]("cats.syntax.FoldableSyntax.catsSyntaxFoldOps") + Seq( + exclude[ReversedMissingMethodProblem]("cats.syntax.FoldableSyntax.catsSyntaxFoldOps"), + exclude[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[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + 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[UpdateForwarderBodyProblem]("cats.Foldable.size"), + exclude[ReversedMissingMethodProblem]("cats.Foldable.unorderedFold"), + exclude[ReversedMissingMethodProblem]("cats.Foldable.unorderedFoldMap"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.unorderedFoldMap"), + 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[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + 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[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.unorderedFoldMap"), + 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[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.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[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + 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[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[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[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[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + 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[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[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[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + 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[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"), + exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"), + 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") + ) } ) From 11b397c81395d3495eebe07fa65eed972476f119 Mon Sep 17 00:00:00 2001 From: LukaJCB Date: Wed, 22 Nov 2017 01:16:40 +0100 Subject: [PATCH 25/27] Fix merging mistake --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a760359959..7c34072da3 100644 --- a/build.sbt +++ b/build.sbt @@ -229,7 +229,7 @@ def mimaSettings(moduleName: String) = Seq( 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[DirectMissingMethodProblem]("cats.data.EitherTInstances2.catsDataMonadErrorForEitherT"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority3"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority2"), exclude[MissingTypesProblem]("cats.data.OneAndLowPriority1"), From 89cab23be292a56b980a2b8d092099e0d1ef8a53 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 24 Nov 2017 15:34:38 +0100 Subject: [PATCH 26/27] Remove toSet --- core/src/main/scala/cats/UnorderedFoldable.scala | 3 --- laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala | 2 -- .../scala/cats/laws/discipline/UnorderedFoldableTests.scala | 1 - 3 files changed, 6 deletions(-) diff --git a/core/src/main/scala/cats/UnorderedFoldable.scala b/core/src/main/scala/cats/UnorderedFoldable.scala index 7701447b68..b0d7270f84 100644 --- a/core/src/main/scala/cats/UnorderedFoldable.scala +++ b/core/src/main/scala/cats/UnorderedFoldable.scala @@ -2,7 +2,6 @@ package cats import cats.kernel.CommutativeMonoid import simulacrum.typeclass -import cats.instances.set._ import cats.instances.long._ /** * `UnorderedFoldable` is like a `Foldable` for unordered containers. @@ -14,8 +13,6 @@ import cats.instances.long._ def unorderedFold[A: CommutativeMonoid](fa: F[A]): A = unorderedFoldMap(fa)(identity) - def toSet[A](fa: F[A]): Set[A] = - unorderedFoldMap(fa)(a => Set(a)) /** * Returns true if there are no elements. Otherwise false. diff --git a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala index d73a79b753..f11c4dfc37 100644 --- a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -50,8 +50,6 @@ trait UnorderedFoldableLaws[F[_]] { !F.isEmpty(fa) || F.forall(fa)(p) } - def toSetRef[A](fa: F[A]): IsEq[Set[A]] = - F.unorderedFoldMap(fa)(a => Set(a)) <-> F.toSet(fa) def nonEmptyRef[A](fa: F[A]): IsEq[Boolean] = F.nonEmpty(fa) <-> !F.isEmpty(fa) diff --git a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala index a094a245db..f154bfeb07 100644 --- a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala @@ -28,7 +28,6 @@ trait UnorderedFoldableTests[F[_]] extends Laws { "unorderedFold consistent with unorderedFoldMap" -> forAll(laws.unorderedFoldConsistentWithUnorderedFoldMap[A] _), "forall consistent with exists" -> forAll(laws.forallConsistentWithExists[A] _), "forall true if empty" -> forAll(laws.forallEmpty[A] _), - "toSet reference" -> forAll(laws.toSetRef[A] _), "nonEmpty reference" -> forAll(laws.nonEmptyRef[A] _) ) } From ea38cd60eb2846d92d6b9b425524433717ee9882 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 24 Nov 2017 17:01:07 +0100 Subject: [PATCH 27/27] Remove unused imports --- laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala | 1 - .../main/scala/cats/laws/discipline/UnorderedFoldableTests.scala | 1 - 2 files changed, 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala index f11c4dfc37..ee98a97708 100644 --- a/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala @@ -1,7 +1,6 @@ package cats package laws -import cats.implicits._ import cats.kernel.CommutativeMonoid trait UnorderedFoldableLaws[F[_]] { diff --git a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala index f154bfeb07..bdbd5c8a88 100644 --- a/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/UnorderedFoldableTests.scala @@ -6,7 +6,6 @@ import org.scalacheck.{Arbitrary, Cogen, Prop} import Prop._ import org.typelevel.discipline.Laws import cats.kernel.CommutativeMonoid -import cats.instances.set._ import cats.instances.boolean._ trait UnorderedFoldableTests[F[_]] extends Laws {