Skip to content

Commit

Permalink
Add UnorderedFoldable and UnorderedTraverse (#1981)
Browse files Browse the repository at this point in the history
* Add CommutativeApply and Applicative and an instance for Validated[CommutativeSemigroup, ?]

* Make Future a CommutativeApplicative

* Revert "Make Future a CommutativeApplicative"

This reverts commit 0cd4eb9.

* Add unordered traversal for Sets

* Test commutativeApply instances + Nested + Tuple2K instances

* Add unordered traversal for Map

* Fix priority for Nested instances

* Fix priority for Tuple2K instances

* Move methods to typeclass

* Deduplicate Applicative instance

* Try out new typeclasses

* Add UnorderedFoldable and UnorderedTraverse and move traversal functions there

* Remove Set from Foldable docs

* Add extra ref law

* Revert "Add UnorderedFoldable and UnorderedTraverse and move traversal functions there"

This reverts commit d308cf7.

* Revert "Remove Set from Foldable docs"

This reverts commit 0ffbc55.

* Add Unordered type classes

* Make UnorderedFoldable and Traverse super classes

* Make unorderedFoldMap the main method

* define isEmpty in terms of exists

* Add mention to unorderedFoldable in foldable scaladoc

* Add size to UnorderedFoldable

* Made exists and forall sufficiently lazy

* Add Mima exceptions

* Fix merging mistake

* Remove toSet

* Remove unused imports
  • Loading branch information
LukaJCB authored and kailuowang committed Nov 25, 2017
1 parent 8cff547 commit d4a3cf5
Show file tree
Hide file tree
Showing 27 changed files with 451 additions and 102 deletions.
35 changes: 35 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,41 @@ def mimaSettings(moduleName: String) = Seq(
import com.typesafe.tools.mima.core.ProblemFilters._
Seq(
exclude[ReversedMissingMethodProblem]("cats.syntax.FoldableSyntax.catsSyntaxFoldOps"),
exclude[ReversedMissingMethodProblem]("cats.Traverse.unorderedTraverse"),
exclude[ReversedMissingMethodProblem]("cats.Traverse.unorderedSequence"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedTraverse.unorderedTraverse"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedTraverse.unorderedSequence"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.toSet"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFold"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable.unorderedFoldMap"),
exclude[IncompatibleResultTypeProblem]("cats.implicits.catsStdInstancesForMap"),
exclude[IncompatibleResultTypeProblem]("cats.implicits.catsStdInstancesForSet"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.toSet"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.unorderedFold"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#Ops.unorderedFoldMap"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedTraverse#Ops.unorderedTraverse"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedTraverse#Ops.unorderedSequence"),
exclude[UpdateForwarderBodyProblem]("cats.Foldable.size"),
exclude[ReversedMissingMethodProblem]("cats.Foldable.unorderedFold"),
exclude[ReversedMissingMethodProblem]("cats.Foldable.unorderedFoldMap"),
exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.size"),
exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.forall"),
exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.isEmpty"),
exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.nonEmpty"),
exclude[DirectMissingMethodProblem]("cats.Foldable#Ops.exists"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#ToUnorderedFoldableOps.toUnorderedFoldableOps"),
exclude[InheritedNewAbstractMethodProblem]("cats.UnorderedFoldable#ToUnorderedFoldableOps.toUnorderedFoldableOps"),
exclude[IncompatibleResultTypeProblem]("cats.instances.SetInstances.catsStdInstancesForSet"),
exclude[ReversedMissingMethodProblem]("cats.instances.SetInstances.cats$instances$SetInstances$_setter_$catsStdInstancesForSet_="),
exclude[ReversedMissingMethodProblem]("cats.instances.SetInstances.catsStdInstancesForSet"),
exclude[DirectMissingMethodProblem]("cats.instances.MapInstances.catsStdInstancesForMap"),
exclude[ReversedMissingMethodProblem]("cats.instances.MapInstances.catsStdInstancesForMap"),
exclude[IncompatibleResultTypeProblem]("cats.instances.package#all.catsStdInstancesForMap"),
exclude[IncompatibleResultTypeProblem]("cats.instances.package#all.catsStdInstancesForSet"),
exclude[IncompatibleResultTypeProblem]("cats.instances.package#map.catsStdInstancesForMap"),
exclude[IncompatibleResultTypeProblem]("cats.instances.package#set.catsStdInstancesForSet"),
exclude[IncompatibleResultTypeProblem]("cats.instances.MapInstances.catsStdInstancesForMap"),
exclude[DirectMissingMethodProblem]("cats.data.EitherTInstances2.catsDataMonadErrorForEitherT"),
exclude[MissingTypesProblem]("cats.data.OneAndLowPriority3"),
exclude[MissingTypesProblem]("cats.data.OneAndLowPriority2"),
exclude[MissingTypesProblem]("cats.data.OneAndLowPriority1"),
Expand Down
29 changes: 13 additions & 16 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats

import scala.collection.mutable
import cats.instances.either._
import cats.instances.long._
import cats.kernel.CommutativeMonoid
import simulacrum.typeclass
import Foldable.sentinel

Expand All @@ -16,6 +16,7 @@ import Foldable.sentinel
* `Foldable[_]` instance.
*
* Instances of Foldable should be ordered collections to allow for consistent folding.
* Use the `UnorderedFoldable` type class if you want to fold over unordered collections.
*
* Foldable[F] is implemented in terms of two basic methods:
*
Expand All @@ -27,7 +28,7 @@ import Foldable.sentinel
*
* See: [[http://www.cs.nott.ac.uk/~pszgmh/fold.pdf A tutorial on the universality and expressiveness of fold]]
*/
@typeclass trait Foldable[F[_]] { self =>
@typeclass trait Foldable[F[_]] extends UnorderedFoldable[F] { self =>

/**
* Left associative fold on 'F' using the function 'f'.
Expand Down Expand Up @@ -55,6 +56,7 @@ import Foldable.sentinel
*/
def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B


/**
* Right associative lazy fold on `F` using the folding function 'f'.
*
Expand Down Expand Up @@ -192,16 +194,6 @@ import Foldable.sentinel
def maximumOption[A](fa: F[A])(implicit A: Order[A]): Option[A] =
reduceLeftOption(fa)(A.max)

/**
* The size of this Foldable.
*
* This is overriden in structures that have more efficient size implementations
* (e.g. Vector, Set, Map).
*
* Note: will not terminate for infinite-sized collections.
*/
def size[A](fa: F[A]): Long = foldMap(fa)(_ => 1)

/**
* Get the element at the index of the `Foldable`.
*/
Expand Down Expand Up @@ -390,7 +382,7 @@ import Foldable.sentinel
*
* If there are no elements, the result is `false`.
*/
def exists[A](fa: F[A])(p: A => Boolean): Boolean =
override def exists[A](fa: F[A])(p: A => Boolean): Boolean =
foldRight(fa, Eval.False) { (a, lb) =>
if (p(a)) Eval.True else lb
}.value
Expand All @@ -400,7 +392,7 @@ import Foldable.sentinel
*
* If there are no elements, the result is `true`.
*/
def forall[A](fa: F[A])(p: A => Boolean): Boolean =
override def forall[A](fa: F[A])(p: A => Boolean): Boolean =
foldRight(fa, Eval.True) { (a, lb) =>
if (p(a)) lb else Eval.False
}.value
Expand Down Expand Up @@ -539,10 +531,10 @@ import Foldable.sentinel
/**
* Returns true if there are no elements. Otherwise false.
*/
def isEmpty[A](fa: F[A]): Boolean =
override def isEmpty[A](fa: F[A]): Boolean =
foldRight(fa, Eval.True)((_, _) => Eval.False).value

def nonEmpty[A](fa: F[A]): Boolean =
override def nonEmpty[A](fa: F[A]): Boolean =
!isEmpty(fa)

/**
Expand Down Expand Up @@ -581,6 +573,11 @@ import Foldable.sentinel
val F = self
val G = Foldable[G]
}

override def unorderedFold[A: CommutativeMonoid](fa: F[A]): A = fold(fa)

override def unorderedFoldMap[A, B: CommutativeMonoid](fa: F[A])(f: (A) => B): B =
foldMap(fa)(f)
}

object Foldable {
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)
}
71 changes: 71 additions & 0 deletions core/src/main/scala/cats/UnorderedFoldable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cats

import cats.kernel.CommutativeMonoid
import simulacrum.typeclass
import cats.instances.long._
/**
* `UnorderedFoldable` is like a `Foldable` for unordered containers.
*/
@typeclass trait UnorderedFoldable[F[_]] {

def unorderedFoldMap[A, B: CommutativeMonoid](fa: F[A])(f: A => B): B

def unorderedFold[A: CommutativeMonoid](fa: F[A]): A =
unorderedFoldMap(fa)(identity)


/**
* Returns true if there are no elements. Otherwise false.
*/
def isEmpty[A](fa: F[A]): Boolean =
exists(fa)(Function.const(true))

def nonEmpty[A](fa: F[A]): Boolean =
!isEmpty(fa)

/**
* Check whether at least one element satisfies the predicate.
*
* If there are no elements, the result is `false`.
*/
def exists[A](fa: F[A])(p: A => Boolean): Boolean =
unorderedFoldMap(fa)(a => Eval.later(p(a)))(UnorderedFoldable.commutativeMonoidEval(UnorderedFoldable.orMonoid))
.value

/**
* Check whether all elements satisfy the predicate.
*
* If there are no elements, the result is `true`.
*/
def forall[A](fa: F[A])(p: A => Boolean): Boolean =
unorderedFoldMap(fa)(a => Eval.later(p(a)))(UnorderedFoldable.commutativeMonoidEval(UnorderedFoldable.andMonoid))
.value

/**
* The size of this UnorderedFoldable.
*
* This is overriden in structures that have more efficient size implementations
* (e.g. Vector, Set, Map).
*
* Note: will not terminate for infinite-sized collections.
*/
def size[A](fa: F[A]): Long = unorderedFoldMap(fa)(_ => 1L)
}

object UnorderedFoldable {
private val orMonoid: CommutativeMonoid[Boolean] = new CommutativeMonoid[Boolean] {
val empty: Boolean = false

def combine(x: Boolean, y: Boolean): Boolean = x || y
}

private val andMonoid: CommutativeMonoid[Boolean] = new CommutativeMonoid[Boolean] {
val empty: Boolean = true

def combine(x: Boolean, y: Boolean): Boolean = x && y
}

private def commutativeMonoidEval[A: CommutativeMonoid]: CommutativeMonoid[Eval[A]] =
new EvalMonoid[A] with CommutativeMonoid[Eval[A]] { val algebra = Monoid[A] }

}
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/UnorderedTraverse.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cats

import simulacrum.typeclass

/**
* `UnorderedTraverse` is like a `Traverse` for unordered containers.
*/
@typeclass trait UnorderedTraverse[F[_]] extends UnorderedFoldable[F] {
def unorderedTraverse[G[_]: CommutativeApplicative, A, B](sa: F[A])(f: A => G[B]): G[F[B]]

def unorderedSequence[G[_]: CommutativeApplicative, A](fga: F[G[A]]): G[F[A]] =
unorderedTraverse(fga)(identity)
}
24 changes: 22 additions & 2 deletions core/src/main/scala/cats/instances/map.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cats
package instances

import cats.kernel.CommutativeMonoid

import scala.annotation.tailrec

trait MapInstances extends cats.kernel.instances.MapInstances {
Expand All @@ -14,8 +16,16 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
}

// scalastyle:off method.length
implicit def catsStdInstancesForMap[K]: FlatMap[Map[K, ?]] =
new FlatMap[Map[K, ?]] {
implicit def catsStdInstancesForMap[K]: UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] =
new UnorderedTraverse[Map[K, ?]] with FlatMap[Map[K, ?]] {

def unorderedTraverse[G[_], A, B](fa: Map[K, A])(f: A => G[B])(implicit G: CommutativeApplicative[G]): G[Map[K, B]] = {
val gba: Eval[G[Map[K, B]]] = Always(G.pure(Map.empty))
val gbb = Foldable.iterateRight(fa, gba){ (kv, lbuf) =>
G.map2Eval(f(kv._2), lbuf)({ (b, buf) => buf + (kv._1 -> b)})
}.value
G.map(gbb)(_.toMap)
}

override def map[A, B](fa: Map[K, A])(f: A => B): Map[K, B] =
fa.map { case (k, a) => (k, f(a)) }
Expand All @@ -39,6 +49,9 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
def flatMap[A, B](fa: Map[K, A])(f: (A) => Map[K, B]): Map[K, B] =
fa.flatMap { case (k, a) => f(a).get(k).map((k, _)) }

def unorderedFoldMap[A, B: CommutativeMonoid](fa: Map[K, A])(f: (A) => B) =
fa.foldLeft(Monoid[B].empty){ case (b, (k, a)) => Monoid[B].combine(b, f(a)) }

def tailRecM[A, B](a: A)(f: A => Map[K, Either[A, B]]): Map[K, B] = {
val bldr = Map.newBuilder[K, B]

Expand All @@ -57,6 +70,13 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
f(a).foreach { case (k, a) => descend(k, a) }
bldr.result
}


override def isEmpty[A](fa: Map[K, A]): Boolean = fa.isEmpty

override def unorderedFold[A](fa: Map[K, A])(implicit A: CommutativeMonoid[A]): A =
A.combineAll(fa.values)

}
// scalastyle:on method.length
}
25 changes: 23 additions & 2 deletions core/src/main/scala/cats/instances/set.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
package cats
package instances

import cats.kernel.CommutativeMonoid

import cats.syntax.show._

trait SetInstances extends cats.kernel.instances.SetInstances {

implicit val catsStdInstancesForSet: MonoidK[Set] =
new MonoidK[Set] {
implicit val catsStdInstancesForSet: UnorderedTraverse[Set] with MonoidK[Set] =
new UnorderedTraverse[Set] with MonoidK[Set] {

def unorderedTraverse[G[_]: CommutativeApplicative, A, B](sa: Set[A])(f: A => G[B]): G[Set[B]] =
sa.foldLeft(Applicative[G].pure(Set.empty[B])) { (acc, a) =>
Apply[G].map2(acc, f(a))(_ + _)
}

override def unorderedSequence[G[_]: CommutativeApplicative, A](sa: Set[G[A]]): G[Set[A]] =
sa.foldLeft(Applicative[G].pure(Set.empty[A])) { (acc, a) =>
Apply[G].map2(acc, a)(_ + _)
}

def empty[A]: Set[A] = Set.empty[A]

def combineK[A](x: Set[A], y: Set[A]): Set[A] = x | y

def unorderedFoldMap[A, B](fa: Set[A])(f: A => B)(implicit B: CommutativeMonoid[B]): B =
fa.foldLeft(B.empty)((b, a) => B.combine(f(a), b))

override def unorderedFold[A](fa: Set[A])(implicit A: CommutativeMonoid[A]): A = A.combineAll(fa)

override def forall[A](fa: Set[A])(p: A => Boolean): Boolean =
fa.forall(p)

override def isEmpty[A](fa: Set[A]): Boolean = fa.isEmpty
}

implicit def catsStdShowForSet[A:Show]: Show[Set[A]] = new Show[Set[A]] {
Expand Down
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
2 changes: 1 addition & 1 deletion docs/src/main/tut/typeclasses/foldable.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit d4a3cf5

Please sign in to comment.