From 07de29206f92a0107fbf23f7f6f04e2eff543297 Mon Sep 17 00:00:00 2001 From: Tomas Mikula Date: Mon, 30 May 2016 20:32:04 -0400 Subject: [PATCH] Make Free.foldMap stack-safe. Fixes #721. The fix is possible by strengthening constraints on the target monad from Monad to MonadRec. --- free/src/main/scala/cats/free/Free.scala | 12 ++++++------ free/src/test/scala/cats/free/FreeTests.scala | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index f34934daa8..93159b07b9 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -135,12 +135,12 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * Run to completion, mapping the suspension with the given transformation at each step and * accumulating into the monad `M`. */ - final def foldMap[M[_]](f: FunctionK[S,M])(implicit M: Monad[M]): M[A] = - step match { - case Pure(a) => M.pure(a) - case Suspend(s) => f(s) - case Gosub(c, g) => M.flatMap(c.foldMap(f))(cc => g(cc).foldMap(f)) - } + final def foldMap[M[_]](f: FunctionK[S,M])(implicit M: MonadRec[M]): M[A] = + M.tailRecM(this)(_.step match { + case Pure(a) => M.pure(Xor.right(a)) + case Suspend(sa) => M.map(f(sa))(Xor.right) + case Gosub(c, g) => M.map(c.foldMap(f))(cc => Xor.left(g(cc))) + }) /** * Compile your Free into another language by changing the suspension functor diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index bf2574ae52..09288b275c 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -51,7 +51,7 @@ class FreeTests extends CatsSuite { fa should === (Free.pure[Option, Int](n)) } - ignore("foldMap is stack safe") { + test("foldMap is stack safe") { trait FTestApi[A] case class TB(i: Int) extends FTestApi[Int]