Skip to content

Commit

Permalink
Add existsM and forallM to Foldable (#1784)
Browse files Browse the repository at this point in the history
* Add `existsM` and `forallM` to Foldable

* update Foldable.existsM/forallM to short-circuit
  • Loading branch information
refried authored and kailuowang committed Aug 7, 2017
1 parent 446d4ed commit 272c684
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 1 deletion.
72 changes: 72 additions & 0 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,78 @@ import simulacrum.typeclass
if (p(a)) lb else Eval.False
}.value

/**
* Check whether at least one element satisfies the effectful predicate.
*
* If there are no elements, the result is `false`. `existsM` short-circuits,
* i.e. once a `true` result is encountered, no further effects are produced.
*
* For example:
*
* {{{
* scala> import cats.implicits._
* scala> val F = Foldable[List]
* scala> F.existsM(List(1,2,3,4))(n => Option(n <= 4))
* res0: Option[Boolean] = Some(true)
*
* scala> F.existsM(List(1,2,3,4))(n => Option(n > 4))
* res1: Option[Boolean] = Some(false)
*
* scala> F.existsM(List(1,2,3,4))(n => if (n <= 2) Option(true) else Option(false))
* res2: Option[Boolean] = Some(true)
*
* scala> F.existsM(List(1,2,3,4))(n => if (n <= 2) Option(true) else None)
* res3: Option[Boolean] = Some(true)
*
* scala> F.existsM(List(1,2,3,4))(n => if (n <= 2) None else Option(true))
* res4: Option[Boolean] = None
* }}}
*/
def existsM[G[_], A](fa: F[A])(p: A => G[Boolean])(implicit G: Monad[G]): G[Boolean] = {
G.tailRecM(Foldable.Source.fromFoldable(fa)(self)) {
src => src.uncons match {
case Some((a, src)) => G.map(p(a))(bb => if (bb) Right(true) else Left(src.value))
case None => G.pure(Right(false))
}
}
}

/**
* Check whether all elements satisfy the effectful predicate.
*
* If there are no elements, the result is `true`. `forallM` short-circuits,
* i.e. once a `false` result is encountered, no further effects are produced.
*
* For example:
*
* {{{
* scala> import cats.implicits._
* scala> val F = Foldable[List]
* scala> F.forallM(List(1,2,3,4))(n => Option(n <= 4))
* res0: Option[Boolean] = Some(true)
*
* scala> F.forallM(List(1,2,3,4))(n => Option(n <= 1))
* res1: Option[Boolean] = Some(false)
*
* scala> F.forallM(List(1,2,3,4))(n => if (n <= 2) Option(true) else Option(false))
* res2: Option[Boolean] = Some(false)
*
* scala> F.forallM(List(1,2,3,4))(n => if (n <= 2) Option(false) else None)
* res3: Option[Boolean] = Some(false)
*
* scala> F.forallM(List(1,2,3,4))(n => if (n <= 2) None else Option(false))
* res4: Option[Boolean] = None
* }}}
*/
def forallM[G[_], A](fa: F[A])(p: A => G[Boolean])(implicit G: Monad[G]): G[Boolean] = {
G.tailRecM(Foldable.Source.fromFoldable(fa)(self)) {
src => src.uncons match {
case Some((a, src)) => G.map(p(a))(bb => if (!bb) Right(false) else Left(src.value))
case None => G.pure(Right(true))
}
}
}

/**
* Convert F[A] to a List[A].
*/
Expand Down
11 changes: 10 additions & 1 deletion tests/src/test/scala/cats/tests/FoldableTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb
}
}

test(s"Foldable[$name].find/exists/forall/filter_/dropWhile_") {
test(s"Foldable[$name].find/exists/forall/existsM/forallM/filter_/dropWhile_") {
forAll { (fa: F[Int], n: Int) =>
fa.find(_ > n) should === (iterator(fa).find(_ > n))
fa.exists(_ > n) should === (iterator(fa).exists(_ > n))
fa.forall(_ > n) should === (iterator(fa).forall(_ > n))
fa.existsM(k => Option(k > n)) should === (Option(iterator(fa).exists(_ > n)))
fa.forallM(k => Option(k > n)) should === (Option(iterator(fa).forall(_ > n)))
fa.filter_(_ > n) should === (iterator(fa).filter(_ > n).toList)
fa.dropWhile_(_ > n) should === (iterator(fa).dropWhile(_ > n).toList)
fa.takeWhile_(_ > n) should === (iterator(fa).takeWhile(_ > n).toList)
Expand Down Expand Up @@ -239,6 +241,13 @@ class FoldableTestsAdditional extends CatsSuite {
assert(concatUntil("Zero" #:: "One" #:: "STOP" #:: boom, "STOP") == Left("ZeroOne"))
}

test(".existsM/.forallM short-circuiting") {
implicit val F = foldableStreamWithDefaultImpl
def boom: Stream[Boolean] = sys.error("boom")
assert(F.existsM[Id, Boolean](true #:: boom)(identity) == true)
assert(F.forallM[Id, Boolean](false #:: boom)(identity) == false)
}

test("Foldable[List] doesn't break substitution") {
val result = List.range(0,10).foldM(List.empty[Int])((accum, elt) => Eval.always(elt :: accum))

Expand Down

0 comments on commit 272c684

Please sign in to comment.