Skip to content

Instantly share code, notes, and snippets.

@aaronlevin
Last active October 23, 2017 20:51
Show Gist options
  • Save aaronlevin/5e92ad6b177efb3a67daa135b1bd99ab to your computer and use it in GitHub Desktop.
Save aaronlevin/5e92ad6b177efb3a67daa135b1bd99ab to your computer and use it in GitHub Desktop.
Scala version of the blog post Resources, Laziness, and Continuation-Passing Style
object cps {
/**
*
* The code below translates this blog post
* http://blog.infinitenegativeutility.com/2016/8/resources--laziness--and-continuation-passing-style)
* into scala, and uses laziness where appropriate to highlight the issue.
*
* I also include a type for IO. This is mainly illustrative, but also ensures
* we "sequence" actions appropriately. Haskell's IO monad works in tandem with the
* runtime to ensure IO actions are sequenced in the correct order (which in the presence
* of laziness is no trivial matter). Anyway, if this part is confusing just ignore it
* and pretend that I'm using IO as a simple way to box a value and let us use for-comprehensions.
*
* Running the code produces the following:
☭ scala -cp classes cps.scala
UNSAFE
==================
Creating resource
Destroying resource
EVALUATING RESPONSE
Sending response
/////////////////////////////
SAFE
==================
Creating resource
EVALUATING RESPONSE
Sending response
Destroying resource
*
* As you can see, in the unsafe mode ,the resource is destroyed
* before it is used.
*/
/**
* lazy values
*/
type Lazy[A] = () => A
/**
* a simple model for IO.
*/
case class IO[A](run: A) {
def map[B](f: A => B): IO[B] = IO(f(run))
def flatMap[B](f: A => IO[B]): IO[B] = f(run)
}
/**
* bracket handles the initialization and cleanup
* of resources. You provide a handler.
*/
def bracket[A,B,C](
initResource: IO[A],
cleanupResource: A => IO[B],
handler: A => IO[C]
): IO[C] = for {
resource <- initResource
response <- handler(resource)
_ <- cleanupResource(resource)
} yield response
/**
* now we're going to make a web-server
* with subtleties about resource manaement.
*/
/**
* example types from the blo post.
*/
object Request
object Response
object SomeResource
/**
* type aliases to avoid having to write .type all the time.
*/
type Request = Request.type
type Response = Response.type
type SomeResource = SomeResource.type
/**
* the main App type. note the Response is "lazy"
* this lazy response is the source of our woes
* which we will solve using continuation passing style
*/
type App = Request => IO[Lazy[Response]]
/**
* run a web application
*/
def runApp(app: App): IO[Unit] = for {
response <- app(Request)
} yield {
response() match {
case Response => println("Sending response")
}
}
/**
* An unsafe application
*/
def unsafeApplication(request: Request): IO[Lazy[Response]] =
bracket[SomeResource, Unit, Lazy[Response]](
{println("Creating resource"); IO(SomeResource)},
SomeResource => {println("Destroying resource"); IO(Unit)},
SomeResource => IO(() => {println("EVALUATING RESPONSE"); Response})
// ^-- note: the lazy response is wrapped in IO.
)
/******* NOW ENTERING THE CPS ZONE **********/
/**
* A new definition of our App, which allows us to have control over
* when the Response is evaluated.
*/
type AppCPS = Request => (Lazy[Response] => IO[Unit]) => IO[Unit]
// ^-- the continuation
/**
* This version of runApp uses the continuation. This allows us to force
* evaluation of the lazy response within the continuation, which will ensure
* proper resource allocation and cleanup.
*/
def runAppCPS(app: AppCPS): IO[Unit] =
app(Request)({ (lazyResponse: Lazy[Response]) =>
// Force evaluation
lazyResponse()
println("Sending response")
IO(())
})
/**
* a CPS'd version of our application: it takes a continuation.
*/
def safeAppCPS(request: Request)(continuation: Lazy[Response] => IO[Unit]): IO[Unit] =
bracket[SomeResource, Unit, Unit](
{println("Creating resource"); IO(SomeResource)},
SomeResource => {println("Destroying resource"); IO(Unit)},
SomeResource => continuation(() => {println("EVALUATING RESPONSE"); Response})
// ^-- note: the lazy response is passed to continuation.
)
def main(args: Array[String]): Unit = {
println("UNSAFE")
println("==================\n")
runApp(unsafeApplication).run
println("\n/////////////////////////////\n")
println("SAFE")
println("==================\n")
runAppCPS(safeAppCPS).run
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment