forked from typelevel/cats
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add UnorderedFoldable and UnorderedTraverse and move traversal functi…
…ons there
- Loading branch information
Luka Jacobowitz
committed
Oct 18, 2017
1 parent
aa49de9
commit d308cf7
Showing
13 changed files
with
324 additions
and
172 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 } | ||
} |
Oops, something went wrong.