Skip to content

Commit

Permalink
Added Defer[FreeT[...]] and used it to ensure handleErrorWith is stac…
Browse files Browse the repository at this point in the history
…k-safe (in and of itself)
  • Loading branch information
djspiewak committed Jan 12, 2020
1 parent ec99437 commit 2b13434
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 1 deletion.
13 changes: 12 additions & 1 deletion free/src/main/scala/cats/free/FreeT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,20 @@ sealed abstract private[free] class FreeTInstances extends FreeTInstances0 {
FreeT.liftT(E.raiseError[A](e))(M)
}

// not to be confused with defer... which is something different... sigh...
implicit def catsDeferForFreeT[S[_], M[_]: Applicative]: Defer[FreeT[S, M, *]] =
new Defer[FreeT[S, M, *]] {
def defer[A](fa: => FreeT[S, M, A]): FreeT[S, M, A] =
FreeT.pure[S, M, Unit](()).flatMap(_ => fa)
}

implicit def catsFreeMonadErrorForFreeT2[S[_], M[_], E](implicit E: MonadError[M, E],
S: Functor[S]): MonadError[FreeT[S, M, *], E] =
new MonadError[FreeT[S, M, *], E] with FreeTMonad[S, M] {
override def M = E

private[this] val RealDefer = catsDeferForFreeT[S, M]

/*
* Quick explanation... The previous version of this function (retained above for
* bincompat) was only able to look at the *top* level M[_] suspension in a Free
Expand All @@ -263,7 +272,9 @@ sealed abstract private[free] class FreeTInstances extends FreeTInstances0 {
val ft = FreeT.liftT[S, M, FreeT[S, M, A]] {
val resultsM = E.map(fa.resume) {
case Left(se) =>
FreeT.liftF[S, M, FreeT[S, M, A]](S.map(se)(handleErrorWith(_)(f))).flatMap(identity)
// we defer here in order to ensure stack-safety in the results even when M[_] is not itself stack-safe
// there's some small performance loss as a consequence, but really, if you care that much about performance, why are you using FreeT?
RealDefer.defer(FreeT.liftF[S, M, FreeT[S, M, A]](S.map(se)(handleErrorWith(_)(f))).flatMap(identity))

case Right(a) =>
pure(a)
Expand Down
2 changes: 2 additions & 0 deletions free/src/test/scala/cats/free/FreeTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class FreeTSuite extends CatsSuite {
SerializableTests.serializable(MonadError[FreeTOption, Unit]))
}

checkAll("FreeT[Option, Option, Int", DeferTests[FreeTOption].defer[Int])

test("FlatMap stack safety tested with 50k flatMaps") {
val expected = Applicative[FreeTOption].pure(())
val result =
Expand Down

0 comments on commit 2b13434

Please sign in to comment.