Skip to content

Commit

Permalink
Add UnorderedFoldable and UnorderedTraverse and move traversal functi…
Browse files Browse the repository at this point in the history
…ons there
  • Loading branch information
Luka Jacobowitz committed Oct 18, 2017
1 parent aa49de9 commit d308cf7
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 172 deletions.
54 changes: 1 addition & 53 deletions core/src/main/scala/cats/CommutativeApplicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,56 +11,4 @@ import simulacrum.typeclass
*
* Must obey the laws defined in cats.laws.CommutativeApplicativeLaws.
*/
@typeclass trait CommutativeApplicative[F[_]] extends Applicative[F] with CommutativeApply[F] {
def traverseUnordered[A, B](sa: Set[A])(f: A => F[B]): F[Set[B]] =
sa.foldLeft(pure(Set.empty[B])) { (acc, a) =>
map2(acc, f(a))(_ + _)
}

def sequenceUnordered[A](sa: Set[F[A]]): F[Set[A]] =
sa.foldLeft(pure(Set.empty[A])) { (acc, a) =>
map2(acc, a)(_ + _)
}


def traverseUnorderedMap[K, L, A, B](sa: Map[K, A])(f: (K, A) => F[(L, B)]): F[Map[L, B]] =
sa.foldLeft(pure(Map.empty[L, B])) { (acc, a) =>
map2(acc, f.tupled(a))(_ + _)
}

def sequenceUnorderedMap[K, L, A](sa: Map[K, F[(K, A)]]): F[Map[K, A]] = {
sa.foldLeft(pure(Map.empty[K, A])) { (acc, a) =>
map2(acc, a._2)(_ + _)
}
}
}

@typeclass trait TraverseUnordered[F[_]] {
def traverseUnordered[G[_]: CommutativeApplicative, A, B](sa: F[A])(f: A => G[B]): G[F[B]]

def sequenceUnordered[G[_]: CommutativeApplicative, A](fga: F[G[A]]): G[F[A]] =
traverseUnordered(fga)(identity)
}

@typeclass trait NonEmptyTraverseUnordered[F[_]] {
def nonEmptyTraverseUnordered[G[_]: CommutativeApply, A, B](sa: F[A])(f: A => G[B]): G[F[B]]

def nonEmptySequenceUnordered[G[_]: CommutativeApply, A](fga: F[G[A]]): G[F[A]] =
nonEmptyTraverseUnordered(fga)(identity)
}

@typeclass trait NonEmptyCommutativeParallel[F[_], M[_]] {
def commutativeApply: CommutativeApply[F]
def commutativeFlatMap: CommutativeFlatMap[M]

def sequential: F ~> M
def parallel: M ~> F
}

@typeclass trait CommutativeParallel[F[_], M[_]] extends NonEmptyCommutativeParallel[F, M] {
def commutativeApplicative: CommutativeApplicative[F]
def commutativeMonad: CommutativeMonad[M]

def commutativeApply = commutativeApplicative
def commutativeFlatMap = commutativeMonad
}
@typeclass trait CommutativeApplicative[F[_]] extends Applicative[F] with CommutativeApply[F]
66 changes: 66 additions & 0 deletions core/src/main/scala/cats/UnorderedFoldable.scala
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
}
}
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/UnorderedTraverse.scala
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)
}
21 changes: 7 additions & 14 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,10 +16,10 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
}

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

def traverse[G[_], A, B](fa: Map[K, A])(f: A => G[B])(implicit G: Applicative[G]): G[Map[K, B]] = {
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.iterator, gba){ (kv, lbuf) =>
G.map2Eval(f(kv._2), lbuf)({ (b, buf) => buf + (kv._1 -> b)})
Expand Down Expand Up @@ -72,22 +74,13 @@ trait MapInstances extends cats.kernel.instances.MapInstances {
bldr.result
}

override def size[A](fa: Map[K, A]): Long = fa.size.toLong

override def get[A](fa: Map[K, A])(idx: Long): Option[A] =
if (idx < 0L || Int.MaxValue < idx) None
else {
val n = idx.toInt
if (n >= fa.size) None
else Some(fa.valuesIterator.drop(n).next)
}

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

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

override def toList[A](fa: Map[K, A]): List[A] = fa.values.toList
override def toSet[A](fa: Map[K, A]): Set[A] = fa.values.toSet
}
// scalastyle:on method.length
}
41 changes: 16 additions & 25 deletions core/src/main/scala/cats/instances/set.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
package cats
package instances

import scala.annotation.tailrec
import cats.kernel.CommutativeMonoid

import cats.syntax.show._

trait SetInstances extends cats.kernel.instances.SetInstances {

implicit val catsStdInstancesForSet: Foldable[Set] with MonoidK[Set] =
new Foldable[Set] with 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))(_ + _)
}

def sequenceUnordered[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]

Expand All @@ -20,36 +30,17 @@ trait SetInstances extends cats.kernel.instances.SetInstances {
def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
Foldable.iterateRight(fa.iterator, lb)(f)

override def get[A](fa: Set[A])(idx: Long): Option[A] = {
@tailrec
def go(idx: Int, it: Iterator[A]): Option[A] = {
if (it.hasNext) {
if (idx == 0) Some(it.next) else {
it.next
go(idx - 1, it)
}
} else None
}
if (idx < Int.MaxValue && idx >= 0L) go(idx.toInt, fa.toIterator) else None
}
def insert[A](fa: Set[A], a: A): Set[A] = fa + a

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

override def exists[A](fa: Set[A])(p: A => Boolean): Boolean =
fa.exists(p)
override def toSet[A](fa: Set[A]): Set[A] = 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

override def fold[A](fa: Set[A])(implicit A: Monoid[A]): A = A.combineAll(fa)

override def toList[A](fa: Set[A]): List[A] = fa.toList

override def reduceLeftOption[A](fa: Set[A])(f: (A, A) => A): Option[A] =
fa.reduceLeftOption(f)

override def find[A](fa: Set[A])(f: A => Boolean): Option[A] = fa.find(f)
}

Expand Down
63 changes: 63 additions & 0 deletions laws/src/main/scala/cats/laws/UnorderedFoldableLaws.scala
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 }
}
62 changes: 62 additions & 0 deletions laws/src/main/scala/cats/laws/UnorderedTraverseLaws.scala
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 }
}
Loading

0 comments on commit d308cf7

Please sign in to comment.