Last active
October 23, 2017 20:51
-
-
Save aaronlevin/5e92ad6b177efb3a67daa135b1bd99ab to your computer and use it in GitHub Desktop.
Scala version of the blog post Resources, Laziness, and Continuation-Passing Style
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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