Skip to content

Commit

Permalink
update Foldable.existsM/forallM to short-circuit
Browse files Browse the repository at this point in the history
  • Loading branch information
aryairani committed Aug 7, 2017
1 parent 6ff5f12 commit 9800e4a
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 16 deletions.
54 changes: 38 additions & 16 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -327,11 +327,10 @@ import simulacrum.typeclass
}.value

/**
* Check whether at least one element satisfies the predicate.
* Check whether at least one element satisfies the effectful predicate.
*
* If there are no elements, the result is `false`.
*
* All effects are sequenced, i.e. later effects aren't skipped even if a `true` is encountered.
* 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:
*
Expand All @@ -341,22 +340,33 @@ import simulacrum.typeclass
* 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))
* res1: Option[Boolean] = Some(true)
* res2: Option[Boolean] = Some(true)
*
* scala> F.existsM(List(1,2,3,4))(n => if (n <= 2) Option(true) else None)
* res2: Option[Boolean] = 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] =
foldM(fa, false)((z, a) => G.map(p(a))(b => b || z))
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 predicate.
*
* If there are no elements, the result is `true`.
* Check whether all elements satisfy the effectful predicate.
*
* All effects are sequenced, i.e. later effects aren't skipped even if a `false` is encountered.
* 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:
*
Expand All @@ -366,15 +376,27 @@ import simulacrum.typeclass
* 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 => if (n <= 2) Option(true) else Option(false))
* 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)
* res2: Option[Boolean] = 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] =
foldM(fa, true)((z, a) => G.map(p(a))(b => b && z))
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
7 changes: 7 additions & 0 deletions tests/src/test/scala/cats/tests/FoldableTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -241,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 9800e4a

Please sign in to comment.