Tree @master (Download .tar.gz)
- ..
- 2014-02-03
- 4d-chess.md
- a-small-haskell-record-complaint
- cherenkov.md
- codata
- commonplace
- daemontools.html
- daemontools.md
- data-files-and-backpack.md
- distributed-git.md
- file-system
- formats.md
- higher-rank-trait-bounds.md
- idiomaticity.md
- in-defense-of-hm
- madlibs-intro.md
- madlibs-ml.md
- madlibs-scheme.md
- multiwhile.md
- orwell.md
- resources-laziness-and-cps.md
- script-based-data-analysis
- semigroup.html
- semigroup.md
- sexprs.md
- shift-resolve-parsing.md
- some-rust-errors.md
- static-versus-dyn
- tansu
- tc-post.md
- tc-post.md.bak
- telml.md
- typeclasses.md
- writer-leak.md
resources-laziness-and-cps.md @master — view markup · raw · history · blame
This is a question posed by Aaron Levin on Reddit:
In WAI, Application has the type
Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
. There is a note about how historically this wasRequest -> ResourceT IO Response
. There is the following note on Hackage about this change:Note that, since WAI 3.0, this type is structured in continuation passing style to allow for proper safe resource handling. This was handled in the past via other means (e.g., ResourceT).
A naive implementation might have
type Application = Request -> IO Response
. I would assume one could still handle appropriate resource cleanup (both on the client side and on the Wai/warp side) in this naive implementation, but I'm assuming I'm wrong in thinking that. So:
- Why is Application defined in CPS style?
- Is there an example of how resources cannot be cleaned up appropriately using a naive implementation above?
- Are there other reasons for the CPS style? (E.g. allows for more efficiency as network resources can be freed while your handler is running)
Like my last post, this is a piece of folklore that's been described in papers, mailing lists, &c, but I don't know of a single resource to point people to, so I'll explain it here. Unlike my last post, I'll do it operationally!
I'm gonna explain some preliminary details about laziness, the trace
function,
and the bracket
function; if you're already familiar with these, go ahead and
skip to the section A Naïve Application Type.
Some Preliminary Explanations
A big part of this post involves the fact that Haskell is lazy: when we write something in Haskell, it won't be evaluated until it's absolutely necessary that it be ready—which might be never! For example, an expression like
>>> let f x y = x in f 5 undefined
5
will happily ignore our use of undefined
and return the value we wanted. If
we want to make sure that a given expression is evaluated, we need to
use it somehow. One way of using is to pattern-match on it: that needs to
evaluate it at least enough that we know what the outermost layer of data
looks like. Let's change the above example a little bit:
>>> let f x y = case y of { () -> x } in f 5 undefined
*** Exception: Prelude.undefined
Now, we're pattern-matching on the y
value, which forces it to—that is,
it causes it to be evaluated before we can evaluate the branch associated
with that case
-expression.
In general—with the exception of code that loops forever, or inherently
unsafe values like undefined
—this should be a detail we don't notice!
In pure code that doesn't loop forever, evaluation order doesn't matter
at all, and imperative code in Haskell uses monads like IO
. Because
each step in a monad has to be evaluated before the next step can proceed,
those computations appear to be strict, and the evaluation order of other
computations is largely opaque.
However, Haskell has a few features we can use to peek into what's going
on under the surface. One of them is the trace
function, found in Debug.Trace
,
which has the signature
trace :: String -> a -> a
This takes a string and any other value, and will print the string
as soon as the value is forced, not before, not after. That makes it
of sometimes questionable utility for debugging, as there's no guarantee
that trace
will even run, much less run at the point we expect!
>>> let f x y = x in f 5 (trace "Hello!" undefined)
5
But because trace
will print exactly when when its result is needed,
we can use it to peek into how Haskell chooses to evaluate our code.
Bracketing for Resources
The module Control.Exception.Base
exports a function called bracket
:
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
This function is used for managing resources that we need to clean up,
like file handles or large pieces of data we don't want to keep in
memory. The first argument is an IO
computation that produces a
resource, the second one is the cleanup function for that resource,
and the third one is a function that uses that resource to produce a
value prior to cleanup:
main :: IO ()
main = bracket
(openFile "tmp.txt" ReadMode) -- opening a resource: here, a file
hClose -- closing the resource
(\ handle -> do -- what to do with the resource
contents <- hGetContents handle
putStrLn contents)
The common function withFile
is implemented straightforwardly
in terms of bracket
almost exactly like I used it above. The
major utility of bracket
is that it knows how to correctly handle
exceptions that occur while running computations: if an exception
gets raised while the resource is being used, it will nevertheless
ensure that the resource gets closed appropriately.
A Naïve Application Type
Let's try writing a toy version of WAI that doesn't actually do anything. We're going to pretend that we're writing some kind of server where some responses might depend on scarce resources, so we want to clean up that resource as soon as we can.
Let's start with importing the functions above and defining some dummy types:
import Control.Exception.Base (bracket)
import Debug.Trace (trace)
-- Our dummy requests and responses
data Request = Request
data Response = Response
-- Our dummy resource
data SomeResource = SomeResource
-- Our naive @App@ type
type App = Request -> IO Response
Let's write our App
handler first. Because we're trying to mimic a
real application, we pattern-match on the Response
value to force
Haskell to evaluate it before returning:
runApp :: App -> IO ()
runApp app = do
response <- app Request
case response of
Response -> putStrLn "Sending response"
Now, let's create an App
that uses some "resource". In this case, I'm
going to fake it, of course. Additionally, I'm going to include a call
to trace
so that we can be sure of when our Response
actually
gets evaluated:
myApp :: App
myApp request = bracket createResource destroyResource respond
where createResource = do
putStrLn "Creating resource"
return SomeResource
destroyResource _ = do
putStrLn "Destroying resource"
respond SomeResource = do
return (trace "EVALUATING RESPONSE" Response)
And now let's wrap this all up:
main :: IO ()
main = runApp myApp
When I run this program on my machine here's what I get:
$ runhaskell myapp.hs
Creating resource
Destroying resource
EVALUATING RESPONSE
Sending response
What does this mean? Well, this means that we end up destroying the resource we are trying to use even before the response gets constructed. That's not good! In this case, the resource is just a dummy value, but if it were instead, say, a file handle, then this program would close the file handle before we ever tried to read from it!
But we're using bracket
and everything! That should mean that
the action we're passing to bracket
—the respond
function we
defined up above—should run before the resource gets destroyed!
Shouldn't it?
As a matter of fact, it is. The IO
action is getting run, and the IO
steps are getting evaluated eagerly. If we rewrite the myApp
function
slightly to insert a print statement there
myApp' :: App
myApp' request = bracket createResource destroyResource respond
where createResource = do
putStrLn "Creating resource"
return SomeResource
destroyResource _ = do
putStrLn "Destroying resource"
respond SomeResource = do
putStrLn "Responding to request" -- A new print statement
return (trace "EVALUATING RESPONSE" Response)
and then run it again, we'll see that the respond
function is getting
properly bracketed between resource creation and destruction:
$ runhaskell myapp.hs
Creating resource
Responding to request
Destroying resource
EVALUATING RESPONSE
Sending response
The problem is that while IO
steps are evaluated eagerly, the values
that get return
ed are subject to the normal rules of laziness:
they aren't forced until we use them. In this case, we don't actually
force the Response
value until the case
-expression in runApp
,
which doesn't happen until after the entire myApp
function—bracket
and all—has finished running.
So the problem is that, while bracket
can ensure that IO
actions
are properly bookended with resource-related code, it can't make the
same guarantees with respect to the values inside it. We need some
way of forcing the value before the cleanup code gets run. We can
rewrite myApp
to do this:
myStricterApp :: App
myStricterApp request = bracket createResource destroyResource respond
where createResource = do
putStrLn "Creating resource"
return SomeResource
destroyResource _ = do
putStrLn "Destroying resource"
respond SomeResource = do
putStrLn "Responding to request" -- A new print statement
let response = trace "EVALUATING RESPONSE" Response
case response of -- pattern-match to force evaluation
Response -> return response
This produces the output we want:
$ runhaskell myapp.hs
Creating resource
Responding to request
EVALUATING RESPONSE
Destroying resource
Sending response
But this has a lot of drawbacks: it's ugly, it inserts apparently
extraneous case statements, and I've papered over the fact that, if we
had a more complicated data structure, we'd need to force evaluation of
the entire thing, not just the outermost structure. But even worse, it's
not a general solution: WAI is a library that exports the equivalent of
runApp
and expects users to write the equivalent of myApp
, which means
we'd be forcing every single user to remember to force their Response
values. If a user forgets to insert the ugly and opaque code, then their
program might start crashing or—even worse—running but nonsensical results.
What we want is some way of ensuring that any App
must force its
Response
before cleaning up any resources.
CPS To The Rescue
The core idea of continuation-passing is that a continuation is a function
that represents "what to do next": a continuation is a callback function
that expects the result of some computation. Let's rewrite our program
but with an App
type that looks like the CPS-ey type WAI actually uses:
-- Our modified @App@ type
type App = Request -> (Response -> IO ()) -> IO ()
Now, our App
takes two arguments—the Request
, as before, as well as
a function that takes a Response
and produces an IO
action. The logic
of "sending" the response now gets moved into the callback function we
pass to the App
:
runApp :: App -> IO ()
runApp app =
app Request
(\ response -> case response of
Response -> putStrLn "Sending response")
Our implementation of myApp
is almost the same, except we take an
extra callback parameter, and instead of returning our Response
, we
pass it to that callback:
myApp :: App
myApp request cb = bracket createResource destroyResource respond
where createResource = do
putStrLn "Creating resource"
return SomeResource
destroyResource _ = do
putStrLn "Destroying resource"
respond SomeResource = do
cb (trace "EVALUATING RESPONSE" Response)
If I run this version of the program, I get this output:
$ runhaskell myapp.hs
Creating resource
EVALUATING RESPONSE
Sending response
Destroying resource
This is much better! This ensures that the resource outlives the
Response
, and because now the callback is responsible for forcing
the Response
, it can guarantee that it gets done. This lets us
ensure that propery of any App
! This is something we couldn't
ensure with the previous one, because it would have required some
way of reaching "inside" the App
to force the return value. By
having a callback that the user has to call, we can force it ourselves
without having to hope that the user does!
…well, except there's that remaining little issue: using the
implementation above, the user doesn't have to call the callback,
so we could write an App
that produces no Response
at all:
myBadApp :: App
myBadApp _ _ = return ()
We can fix this by creating a new type—WAI calls it ResponseReceived
—and
hiding every way to construct it except using the callback. That way, the user
has to use the callback at least once in order to get a ResponseReceived
value, which they can return from their App
.
(We will have the problem that we can call the callback more than
once: we could keep working on solutions, but we'd have to move to more
complicated solutions and WAI ends up suggesting that you just not do that.)
So this is why the CPS style is indeed necessary for resource management here:
it's not enough to use bracket
, you have to use bracket
and have the
appropriate amount of strictness, and the CPS style helps you achieve that.