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") {