Skip to content

Commit ffded4b

Browse files
denisroscaLukaJCB
authored andcommitted
Add foldRightDefer to Foldable (#2772)
* 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
1 parent f1ead7d commit ffded4b

File tree

4 files changed

+34
-1
lines changed

4 files changed

+34
-1
lines changed

core/src/main/scala/cats/Foldable.scala

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ import Foldable.sentinel
9595
*/
9696
def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B]
9797

98+
def foldRightDefer[G[_]: Defer, A, B](fa: F[A], gb: G[B])(fn: (A, G[B]) => G[B]): G[B] =
99+
Defer[G].defer(
100+
this.foldLeft(fa, (z: G[B]) => z) { (acc, elem) => z =>
101+
Defer[G].defer(acc(fn(elem, z)))
102+
}(gb)
103+
)
104+
98105
def reduceLeftToOption[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): Option[B] =
99106
foldLeft(fa, Option.empty[B]) {
100107
case (Some(b), a) => Some(g(b, a))
@@ -630,6 +637,13 @@ object Foldable {
630637
Eval.always(iterable.iterator).flatMap(loop)
631638
}
632639

640+
def iterateRightDefer[G[_]: Defer, A, B](iterable: Iterable[A], lb: G[B])(f: (A, G[B]) => G[B]): G[B] = {
641+
def loop(it: Iterator[A]): G[B] =
642+
Defer[G].defer(if (it.hasNext) f(it.next(), Defer[G].defer(loop(it))) else Defer[G].defer(lb))
643+
644+
Defer[G].defer(loop(iterable.iterator))
645+
}
646+
633647
/**
634648
* Isomorphic to
635649
*

laws/src/main/scala/cats/laws/FoldableLaws.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[F] {
4747
): IsEq[B] =
4848
F.foldM[Id, A, B](fa, b)(f) <-> F.foldLeft(fa, b)(f)
4949

50+
def foldRightDeferConsistentWithFoldRight[A, B](
51+
fa: F[A],
52+
f: (B, A) => B
53+
)(implicit
54+
M: Monoid[B]): IsEq[B] = {
55+
val g: (A, Eval[B]) => Eval[B] = (a, ea) => ea.map(f(_, a))
56+
57+
F.foldRight(fa, Later(M.empty))(g).value <-> F.foldRightDefer(fa, Later(M.empty): Eval[B])(g).value
58+
}
59+
5060
/**
5161
* `reduceLeftOption` consistent with `reduceLeftToOption`
5262
*/
@@ -121,6 +131,7 @@ trait FoldableLaws[F[_]] extends UnorderedFoldableLaws[F] {
121131
def orderedConsistency[A: Eq](x: F[A], y: F[A])(implicit ev: Eq[F[A]]): IsEq[List[A]] =
122132
if (x === y) (F.toList(x) <-> F.toList(y))
123133
else List.empty[A] <-> List.empty[A]
134+
124135
}
125136

126137
object FoldableLaws {

laws/src/main/scala/cats/laws/discipline/FoldableTests.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ trait FoldableTests[F[_]] extends UnorderedFoldableTests[F] {
4242
"takeWhile_ reference" -> forAll(laws.takeWhile_Ref[A] _),
4343
"dropWhile_ reference" -> forAll(laws.dropWhile_Ref[A] _),
4444
"collectFirstSome reference" -> forAll(laws.collectFirstSome_Ref[A, B] _),
45-
"collectFirst reference" -> forAll(laws.collectFirst_Ref[A, B] _)
45+
"collectFirst reference" -> forAll(laws.collectFirst_Ref[A, B] _),
46+
"foldRightDefer consistency" -> forAll(laws.foldRightDeferConsistentWithFoldRight[A, B] _)
4647
)
4748
}
4849

tests/src/test/scala/cats/tests/FoldableSuite.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,13 @@ class FoldableSuiteAdditional extends CatsSuite with ScalaVersionSpecificFoldabl
308308
// safely build large lists
309309
val larger = F.foldRight(large, Now(List.empty[Int]))((x, lxs) => lxs.map((x + 1) :: _))
310310
larger.value should ===(large.map(_ + 1))
311+
312+
val sum = F.foldRightDefer(large, Eval.later(0))((elem, acc) => acc.map(_ + elem))
313+
sum.value should ===(large.sum)
314+
315+
def boom[A]: Eval[A] = Eval.later(sys.error("boom"))
316+
// Ensure that the lazy param is actually handled lazily
317+
val lazySum: Eval[Int] = F.foldRightDefer(large, boom[Int])((elem, acc) => acc.map(_ + elem))
311318
}
312319

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

0 commit comments

Comments
 (0)