From 89f4b8979d681c4c612d5bfe634d5286776cb945 Mon Sep 17 00:00:00 2001 From: Chris Birchall Date: Sat, 12 Jan 2019 15:36:16 +0000 Subject: [PATCH 1/4] Add a `defer` factory method to `ContT` Similar to the existing `pure` function, but evaluation of the argument is deferred. This is useful for building a computation which calls its continuation as the final step. Instead of writing: ``` ContT.apply { next => val x = foo() val y = bar(x) val z = baz(y) next(z) } ``` you can write: ``` ContT.defer { val x = foo() val y = bar(x) baz(y) } ``` --- core/src/main/scala/cats/data/ContT.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/scala/cats/data/ContT.scala b/core/src/main/scala/cats/data/ContT.scala index d244fa8687..2e68d840fd 100644 --- a/core/src/main/scala/cats/data/ContT.scala +++ b/core/src/main/scala/cats/data/ContT.scala @@ -70,6 +70,9 @@ object ContT { cb(b) } + def defer[M[_], A, B](b: => B)(implicit M: Defer[ContT[M, A, ?]]): ContT[M, A, B] = + M.defer(pure(b)) + def apply[M[_], A, B](fn: (B => M[A]) => M[A]): ContT[M, A, B] = FromFn(AndThen(fn)) From 4ba3a12c08c4b0da263a7c9e52c8db4fdfe9ac24 Mon Sep 17 00:00:00 2001 From: Chris Birchall Date: Tue, 15 Jan 2019 21:20:40 +0000 Subject: [PATCH 2/4] Add some Scaladoc --- core/src/main/scala/cats/data/ContT.scala | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/core/src/main/scala/cats/data/ContT.scala b/core/src/main/scala/cats/data/ContT.scala index 2e68d840fd..9476dfd990 100644 --- a/core/src/main/scala/cats/data/ContT.scala +++ b/core/src/main/scala/cats/data/ContT.scala @@ -65,17 +65,60 @@ object ContT { lazy val runAndThen: AndThen[B => M[A], M[A]] = loop(next).runAndThen } + /** Lift a pure value into `ContT` */ def pure[M[_], A, B](b: B): ContT[M, A, B] = apply { cb => cb(b) } + /** + * Similar to [[pure]] but evaluation of the argument is deferred. + * + * This is useful for building a computation which calls its continuation as the final step. + * Instead of writing: + * + * {{{ + * ContT.apply { cb => + * val x = foo() + * val y = bar(x) + * val z = baz(y) + * cb(z) + * } + * }}} + * + * you can write: + * + * {{{ + * ContT.defer { + * val x = foo() + * val y = bar(x) + * baz(y) + * } + * }}} + */ def defer[M[_], A, B](b: => B)(implicit M: Defer[ContT[M, A, ?]]): ContT[M, A, B] = M.defer(pure(b)) + /** + * Build a computation that makes use of a callback, also known as a continuation. + * + * Example: + * + * {{{ + * ContT.apply { callback => + * for { + * a <- doFirstThing() + * b <- doSecondThing(a) + * c <- callback(b) + * d <- doFourthThing(c) + * } yield d + * } + * }}} + */ def apply[M[_], A, B](fn: (B => M[A]) => M[A]): ContT[M, A, B] = FromFn(AndThen(fn)) + /** Similar to [[apply]] but evaluation of the argument is deferred. */ def later[M[_], A, B](fn: => (B => M[A]) => M[A]): ContT[M, A, B] = DeferCont(() => FromFn(AndThen(fn))) From bbf90b9f45576928652ac66df591103931b9fcd2 Mon Sep 17 00:00:00 2001 From: Chris Birchall Date: Tue, 15 Jan 2019 21:43:25 +0000 Subject: [PATCH 3/4] Add a unit test for ContT.defer --- tests/src/test/scala/cats/tests/ContTSuite.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ContTSuite.scala b/tests/src/test/scala/cats/tests/ContTSuite.scala index e4e6e28585..af8df13cea 100644 --- a/tests/src/test/scala/cats/tests/ContTSuite.scala +++ b/tests/src/test/scala/cats/tests/ContTSuite.scala @@ -64,4 +64,19 @@ class ContTSuite extends CatsSuite { withContLaw[Eval, Int, String, Int] } + test("ContT.defer defers evaluation until run is invoked") { + forAll { (b: Int, cb: Int => Eval[String]) => + var didSideEffect = false + + val contT = ContT.defer[Eval, String, Int] { + didSideEffect = true + b + } + didSideEffect should ===(false) + + contT.run(cb) + didSideEffect should ===(true) + } + } + } From 5f2f5e109a0378e99bda123f9c33003654b0795a Mon Sep 17 00:00:00 2001 From: Chris Birchall Date: Tue, 15 Jan 2019 21:44:20 +0000 Subject: [PATCH 4/4] Simplify ContT.defer There's no need to use a DeferCont here. --- core/src/main/scala/cats/data/ContT.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/ContT.scala b/core/src/main/scala/cats/data/ContT.scala index 9476dfd990..6aa0dd91ac 100644 --- a/core/src/main/scala/cats/data/ContT.scala +++ b/core/src/main/scala/cats/data/ContT.scala @@ -96,8 +96,10 @@ object ContT { * } * }}} */ - def defer[M[_], A, B](b: => B)(implicit M: Defer[ContT[M, A, ?]]): ContT[M, A, B] = - M.defer(pure(b)) + def defer[M[_], A, B](b: => B): ContT[M, A, B] = + apply { cb => + cb(b) + } /** * Build a computation that makes use of a callback, also known as a continuation.