Skip to content

Commit

Permalink
Add foldRightDefer to Foldable (#2772)
Browse files Browse the repository at this point in the history
* Add Foldable.foldRightDefer

* Fix foldRightDefer <-> foldRight consistency

* Make Foldable.iterateRightDefer lazy

* Fix MiMa incompatibilities

* Revert "Fix MiMa incompatibilities"

This reverts commit 9528be6.

* Change back iterateRight implementation to use Eval directly
  • Loading branch information
denisrosca authored and LukaJCB committed Nov 12, 2019
1 parent f1ead7d commit ffded4b
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 1 deletion.
14 changes: 14 additions & 0 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ import Foldable.sentinel
*/
def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B]

def foldRightDefer[G[_]: Defer, A, B](fa: F[A], gb: G[B])(fn: (A, G[B]) => G[B]): G[B] =
Defer[G].defer(
this.foldLeft(fa, (z: G[B]) => z) { (acc, elem) => z =>
Defer[G].defer(acc(fn(elem, z)))
}(gb)
)

def reduceLeftToOption[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): Option[B] =
foldLeft(fa, Option.empty[B]) {
case (Some(b), a) => Some(g(b, a))
Expand Down Expand Up @@ -630,6 +637,13 @@ object Foldable {
Eval.always(iterable.iterator).flatMap(loop)
}

def iterateRightDefer[G[_]: Defer, A, B](iterable: Iterable[A], lb: G[B])(f: (A, G[B]) => G[B]): G[B] = {
def loop(it: Iterator[A]): G[B] =
Defer[G].defer(if (it.hasNext) f(it.next(), Defer[G].defer(loop(it))) else Defer[G].defer(lb))

Defer[G].defer(loop(iterable.iterator))
}

/**
* Isomorphic to
*
Expand Down
11 changes: 11 additions & 0 deletions laws/src/main/scala/cats/laws/FoldableLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[F] {
): IsEq[B] =
F.foldM[Id, A, B](fa, b)(f) <-> F.foldLeft(fa, b)(f)

def foldRightDeferConsistentWithFoldRight[A, B](
fa: F[A],
f: (B, A) => B
)(implicit
M: Monoid[B]): IsEq[B] = {
val g: (A, Eval[B]) => Eval[B] = (a, ea) => ea.map(f(_, a))

F.foldRight(fa, Later(M.empty))(g).value <-> F.foldRightDefer(fa, Later(M.empty): Eval[B])(g).value
}

/**
* `reduceLeftOption` consistent with `reduceLeftToOption`
*/
Expand Down Expand Up @@ -121,6 +131,7 @@ trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[F] {
def orderedConsistency[A: Eq](x: F[A], y: F[A])(implicit ev: Eq[F[A]]): IsEq[List[A]] =
if (x === y) (F.toList(x) <-> F.toList(y))
else List.empty[A] <-> List.empty[A]

}

object FoldableLaws {
Expand Down
3 changes: 2 additions & 1 deletion laws/src/main/scala/cats/laws/discipline/FoldableTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ trait FoldableTests[F[_]] extends UnorderedFoldableTests[F] {
"takeWhile_ reference" -> forAll(laws.takeWhile_Ref[A] _),
"dropWhile_ reference" -> forAll(laws.dropWhile_Ref[A] _),
"collectFirstSome reference" -> forAll(laws.collectFirstSome_Ref[A, B] _),
"collectFirst reference" -> forAll(laws.collectFirst_Ref[A, B] _)
"collectFirst reference" -> forAll(laws.collectFirst_Ref[A, B] _),
"foldRightDefer consistency" -> forAll(laws.foldRightDeferConsistentWithFoldRight[A, B] _)
)
}

Expand Down
7 changes: 7 additions & 0 deletions tests/src/test/scala/cats/tests/FoldableSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@ class FoldableSuiteAdditional extends CatsSuite with ScalaVersionSpecificFoldabl
// safely build large lists
val larger = F.foldRight(large, Now(List.empty[Int]))((x, lxs) => lxs.map((x + 1) :: _))
larger.value should ===(large.map(_ + 1))

val sum = F.foldRightDefer(large, Eval.later(0))((elem, acc) => acc.map(_ + elem))
sum.value should ===(large.sum)

def boom[A]: Eval[A] = Eval.later(sys.error("boom"))
// Ensure that the lazy param is actually handled lazily
val lazySum: Eval[Int] = F.foldRightDefer(large, boom[Int])((elem, acc) => acc.map(_ + elem))
}

def checkMonadicFoldsStackSafety[F[_]](fromRange: Range => F[Int])(implicit F: Foldable[F]): Unit = {
Expand Down

0 comments on commit ffded4b

Please sign in to comment.