diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 9d2e1e10d9..e3d2d4dab8 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -97,12 +97,15 @@ import scala.annotation.implicitNotFound */ 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( - foldLeft(fa, (z: G[B]) => z) { (acc, elem) => z => - Defer[G].defer(acc(fn(elem, z))) - }(gb) - ) + def foldRightDefer[G[_]: Defer, A, B](fa: F[A], gb: G[B])(fn: (A, G[B]) => G[B]): G[B] = { + def loop(source: Source[A]): G[B] = { + source.uncons match { + case Some((next, s)) => fn(next, Defer[G].defer(loop(s.value))) + case None => gb + } + } + Defer[G].defer(loop(Source.fromFoldable(fa)(self))) + } def reduceLeftToOption[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): Option[B] = foldLeft(fa, Option.empty[B]) { diff --git a/tests/src/test/scala-2.13+/cats/tests/LazyListSuite.scala b/tests/src/test/scala-2.13+/cats/tests/LazyListSuite.scala index ea5f545446..6232c2543c 100644 --- a/tests/src/test/scala-2.13+/cats/tests/LazyListSuite.scala +++ b/tests/src/test/scala-2.13+/cats/tests/LazyListSuite.scala @@ -17,8 +17,11 @@ import cats.laws.discipline.{ import cats.laws.discipline.arbitrary._ import cats.syntax.show._ import cats.syntax.eq._ +import cats.syntax.foldable._ import org.scalacheck.Prop._ +import scala.util.control.TailCalls + class LazyListSuite extends CatsSuite { checkAll("LazyList[Int]", SemigroupalTests[LazyList].semigroupal[Int, Int, Int]) checkAll("Semigroupal[LazyList]", SerializableTests.serializable(Semigroupal[LazyList])) @@ -52,6 +55,16 @@ class LazyListSuite extends CatsSuite { assert(LazyList.empty[Int].show === (s"LazyList()")) } + test("Avoid all evaluation of LazyList#foldRightDefer") { + val sum = LazyList + .from(1) + .foldRightDefer(TailCalls.done(0)) { (elem, acc) => + if (elem <= 100) acc.map(_ + elem) else TailCalls.done(0) + } + .result + (1 to 100).sum === sum + } + test("Show[LazyList] is referentially transparent, unlike LazyList.toString") { forAll { (lazyList: LazyList[Int]) => if (!lazyList.isEmpty) {