Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UnorderedFoldable and UnorderedTraverse #1981

Merged
merged 41 commits into from
Nov 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
0ea4ac0
Add CommutativeApply and Applicative and an instance for Validated[Co…
Sep 24, 2017
0cd4eb9
Make Future a CommutativeApplicative
Sep 28, 2017
f9a239a
Revert "Make Future a CommutativeApplicative"
Sep 28, 2017
965f9c2
Add unordered traversal for Sets
Sep 30, 2017
d3a803a
Test commutativeApply instances + Nested + Tuple2K instances
Sep 30, 2017
1ab4f47
Add unordered traversal for Map
Oct 2, 2017
2164b51
Fix priority for Nested instances
Oct 2, 2017
7f1f6c7
Fix priority for Tuple2K instances
Oct 2, 2017
75a656a
Merge branch 'master' into add-commutative-apply
Oct 3, 2017
69c6374
Merge branch 'master' into add-commutative-apply
Oct 14, 2017
fa8a731
Move methods to typeclass
Oct 14, 2017
2aeddd7
Deduplicate Applicative instance
Oct 17, 2017
aa49de9
Try out new typeclasses
Oct 17, 2017
d308cf7
Add UnorderedFoldable and UnorderedTraverse and move traversal functi…
Oct 18, 2017
148c113
Merge branch 'master' into add-commutative-apply
Oct 18, 2017
0ffbc55
Remove Set from Foldable docs
Oct 19, 2017
23cb589
Add extra ref law
Oct 19, 2017
e0069a8
Merge branch 'master' into add-commutative-apply
Oct 19, 2017
376a5c6
Revert "Add UnorderedFoldable and UnorderedTraverse and move traversa…
Oct 19, 2017
d673655
Revert "Remove Set from Foldable docs"
Oct 19, 2017
8666c64
Add Unordered type classes
Oct 19, 2017
74b0228
Make UnorderedFoldable and Traverse super classes
Oct 19, 2017
dfa8bd3
Make unorderedFoldMap the main method
Oct 20, 2017
903694d
define isEmpty in terms of exists
Oct 20, 2017
821424d
Merge branch 'master' into add-unordered-classes
Oct 20, 2017
a1cdf2d
Merge branch 'master' into add-unordered-classes
Oct 27, 2017
cdc6586
Merge branch 'add-unordered-classes' of https://github.com/LukaJCB/ca…
Oct 27, 2017
3e705d4
Merge branch 'master' into add-unordered-classes
Oct 31, 2017
5f4d41f
Add mention to unorderedFoldable in foldable scaladoc
Oct 31, 2017
cd2e1f5
Merge branch 'master' into add-unordered-classes
kailuowang Nov 9, 2017
5d9f0f7
Add size to UnorderedFoldable
Nov 10, 2017
f70fe6f
Merge branch 'add-unordered-classes' of https://github.com/LukaJCB/ca…
Nov 10, 2017
04722f0
Made exists and forall sufficiently lazy
Nov 14, 2017
9568933
Add Mima exceptions
Nov 14, 2017
9666a0c
Merge branch 'master' into add-unordered-classes
Nov 14, 2017
23a6ca1
Merge branch 'master' into add-unordered-classes
Nov 16, 2017
d2037e6
Merge branch 'master' into add-unordered-classes
kailuowang Nov 21, 2017
11b397c
Fix merging mistake
Nov 22, 2017
89cab23
Remove toSet
Nov 24, 2017
e2984b4
Merge branch 'add-unordered-classes' of https://github.com/LukaJCB/ca…
Nov 24, 2017
ea38cd6
Remove unused imports
Nov 24, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be implemented with foldMap (map to true, do the commutative “or” monoid

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 =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be implemented with unorderedFoldMap

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 =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be implemented with foldMap using and monoid.

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