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) + } }