Type safe continuation passing style
TweetOne common mistake in JavaScript programming is to forget to invoke callback in continuation passing style code. For example, the code below may never complete:
async.series([
function (callback) {
if (..)
console.log("invalid input"); // BUG: NO callback!
else
callback(null, 'ok');
},
function (callback) {
...
}
],
function (err, result) { handleErrorOrResult(err, result);} // Might not be reached
);Unfortunately, there is no systematic way to prevent this kind of bug in JavaScript. We can write tests, but it is not practical to write tests which cover all control paths.
But in Haskell, thanks to the powerful type system, we can turn these bugs into type errors! Let’s take a look at the definition of Application from Web Application Interface.
type Application =
Request ->
(Response -> IO ResponseReceived) ->
IO ResponseReceivedThis signature of Application looks similar to bracket function. Wai uses continuation passing style to handle resource management in an exception-safe manner.
There is a bonus here. A valid function of Application must return a ResponseReceived, but we can’t create one by ourselves because there is no constructor available. The only way to acquire an ResponseReceived value is to invoke the callback. Thus if you accidentally forget to invoke callback, it automatically becomes a type error.
The code snippet below returns responseReceived returned from respond to make application type-check. Otherwise, GHC will complain about the type mismatch.
{-# LANGUAGE OverloadedStrings #-}
import Blaze.ByteString.Builder (fromByteString)
import Network.HTTP.Types (status200)
import Network.Wai
import Network.Wai.Handler.Warp (run)
application _ respond = do
msg = fromByteString "Hello world!"
responseReceived <- respond $ responseBuilder
status200
[("Content-Type", "text/plain")]
msg
return responseReceived
main = run 3000 applicationNOTE: We could define Application using RankNTypes GHC extension instead of ResponseReceived type. An old version of Wai actually used this definition of Application.
Application = Request -> (forall b. (Response -> IO b) -> IO b)