Skip to content

Commit

Permalink
Merge pull request #2696 from cb372/ContT-defer
Browse files Browse the repository at this point in the history
Add a `defer` factory method to `ContT`
  • Loading branch information
kailuowang authored Jan 29, 2019
2 parents af17a10 + 5f2f5e1 commit 2f65265
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 0 deletions.
48 changes: 48 additions & 0 deletions core/src/main/scala/cats/data/ContT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,62 @@ 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): ContT[M, A, B] =
apply { cb =>
cb(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)))

Expand Down
15 changes: 15 additions & 0 deletions tests/src/test/scala/cats/tests/ContTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

}

0 comments on commit 2f65265

Please sign in to comment.