Skip to content

Commit

Permalink
Make UnorderedFoldable and Traverse super classes
Browse files Browse the repository at this point in the history
  • Loading branch information
Luka Jacobowitz committed Oct 20, 2017
1 parent 8666c64 commit 74b0228
Show file tree
Hide file tree
Showing 14 changed files with 62 additions and 73 deletions.
22 changes: 12 additions & 10 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

/**
Expand All @@ -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'.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)

/**
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 7 additions & 1 deletion core/src/main/scala/cats/Traverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/syntax/foldable.scala
Original file line number Diff line number Diff line change
@@ -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)
}
Expand Down
33 changes: 1 addition & 32 deletions laws/src/main/scala/cats/laws/FoldableLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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](
Expand All @@ -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
Expand All @@ -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`.
Expand Down
2 changes: 1 addition & 1 deletion laws/src/main/scala/cats/laws/TraverseLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]] = {
Expand Down
14 changes: 5 additions & 9 deletions laws/src/main/scala/cats/laws/discipline/FoldableTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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] _),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]],
Expand All @@ -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]])] {
Expand Down
6 changes: 3 additions & 3 deletions laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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",
Expand Down
18 changes: 12 additions & 6 deletions laws/src/main/scala/cats/laws/discipline/TraverseTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,38 @@ 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],
EqA: Eq[A],
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]])] {
Expand All @@ -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] _),
Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/ListSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")(
Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/NestedSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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, ?]]))
}

Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/QueueSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/StreamSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down
2 changes: 1 addition & 1 deletion tests/src/test/scala/cats/tests/VectorSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down

0 comments on commit 74b0228

Please sign in to comment.