From 025867d0e3340b0b0fcfd1d091710f4a574b6397 Mon Sep 17 00:00:00 2001 From: Patrick Oscar Boykin Date: Sun, 22 Sep 2024 11:44:30 -1000 Subject: [PATCH 1/2] Add Defer.recursiveFn to aid in recursion --- core/src/main/scala/cats/Defer.scala | 23 +++++++++++++++++++ .../src/test/scala/cats/tests/EvalSuite.scala | 13 +++++++++++ 2 files changed, 36 insertions(+) diff --git a/core/src/main/scala/cats/Defer.scala b/core/src/main/scala/cats/Defer.scala index 01adbee1d7..518e54f5dc 100644 --- a/core/src/main/scala/cats/Defer.scala +++ b/core/src/main/scala/cats/Defer.scala @@ -72,6 +72,29 @@ trait Defer[F[_]] extends Serializable { lazy val res: F[A] = fn(defer(res)) res } + + /** + * Useful when you want a recursive function that returns F where + * F[_]: Defer. Examples include IO, Eval, or transformers such + * as EitherT or OptionT. + * + * example: + * + * val sumTo: Int => Eval[Int] = + * Defer[Eval].recursiveFn[Int, Int] { recur => + * + * { i => + * if (i > 0) recur(i - 1).map(_ + i) + * else Eval.now(0) + * } + * } + */ + def recursiveFn[A, B](fn: (A => F[B]) => (A => F[B])): A => F[B] = + new Function1[A, F[B]] { self => + lazy val loopFn: A => F[B] = fn(self) + + def apply(a: A): F[B] = defer(loopFn(a)) + } } object Defer { diff --git a/tests/shared/src/test/scala/cats/tests/EvalSuite.scala b/tests/shared/src/test/scala/cats/tests/EvalSuite.scala index f64a780051..3819f36e2e 100644 --- a/tests/shared/src/test/scala/cats/tests/EvalSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/EvalSuite.scala @@ -297,4 +297,17 @@ class EvalSuite extends CatsSuite { assert(n2 == 1) } } + + test("test Defer.recursiveFn example") { + val sumTo: Int => Eval[Int] = + cats.Defer[Eval].recursiveFn[Int, Int] { recur => + + { i => + if (i > 0) recur(i - 1).map(_ + i) + else Eval.now(0) + } + } + + assert(sumTo(300000).value == (0 to 300000).sum) + } } From e45b590bf1775da896dc58f27528f706eb5be447 Mon Sep 17 00:00:00 2001 From: Patrick Oscar Boykin Date: Sun, 22 Sep 2024 11:51:17 -1000 Subject: [PATCH 2/2] actually, there is no reason to make loopFn lazy --- core/src/main/scala/cats/Defer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/Defer.scala b/core/src/main/scala/cats/Defer.scala index 518e54f5dc..a4fd3da24d 100644 --- a/core/src/main/scala/cats/Defer.scala +++ b/core/src/main/scala/cats/Defer.scala @@ -91,7 +91,7 @@ trait Defer[F[_]] extends Serializable { */ def recursiveFn[A, B](fn: (A => F[B]) => (A => F[B])): A => F[B] = new Function1[A, F[B]] { self => - lazy val loopFn: A => F[B] = fn(self) + val loopFn: A => F[B] = fn(self) def apply(a: A): F[B] = defer(loopFn(a)) }