As I alluded to in my previous post on continuations I was interested in
them due to a suspicion that they might offer a nice way of dealing with an
issue that came up with some code I was writing.
I spent some time the other night playing with continuations and I re-used the
problem from an earlier post. There are a few differences this time
around though, the main one is that there is no need to create a list of
characters instead the goal is to string together the reading and the
processing using continuations.
First off I should mention that I stuck -fno-monomorphism-restriction in my
source file. Without it I got a lot of complaints from GHC.
I started with the most basic set of functions, one for reading a
character and one for writing a character. I want the type to be ContT r IO
Char for the former and ContT r IO () for the latter, but as always I don’t
actually need to tell the compiler the exact types. Basically they correspond
to getChat and putChar that way:
cpsGetChar = liftIO getChar
cpsPutChar c = liftIO $ putChar c
Running these two in sequence is done like this:
runContT (cpsGetChar >>= cpsPutChar) return
Running it just once isn’t enough though and I was pointed to the function
forever by Cale in #haskell. It wasn’t actually in the version of GHC I have
on my system (it’ll be in 6.7) so I had to define it:
forever x = x >> forever x
Using that it’s now easy to keep on reading an writing individual characters
forever:
runContT (forever $ cpsGetChar >>= cpsPutChar) return
Since that doesn’t ever terminate it isn’t really what I want. Instead I want
the two functions to be “mutually recursive with continuation”, for that I
need to control the continuations more than return allows, I need to use
callCC. First I started by rewriting the functions from above using
callCC. That turned out like this:
cpsGetCharCC = callCC $
\ k -> liftIO getChar >>= cpsPutCharCC >>= k
cpsPutCharCC c = callCC $
\ k -> (liftIO $ putChar c) >> cpsGetCharCC >>= k
Running it should produce the same result as when using forever above:
runContT cpsGetCharCC return
Basically both functions defer calling the actual continuation until a call
to the either cpsPutCharCC or cpsGetCharCC has been made. Still this never
terminates, but rewriting the first function to terminate is simple:
cpsGetCharCC = do
c <- liftIO getChar
if c == 'q'
then return ()
else callCC $ \ k -> cpsPutCharCC c >>= k
Now, one thing that’s possible to do here, but which would be difficult to
accomplish when mapping the handler on a list is to terminate in
cpsPutCharCC. All that is required is to rewrite the handler similarly to
what I just did with cpsGetCharCC:
cpsPutCharCC c = do
liftIO $ putChar c
if c == 'x'
then return ()
else callCC $ \ k -> cpsGetCharCC >>= k
Yes, there isn’t really that mcuh to all of this. Sorry if I’ve disappointed.
The same functionality could be achieved using two regular mutually recursive
functions:
mutGetChar = do
c <- getChar
if c == 'q'
then return ()
else mutPutChar c
mutPutChar c = do
putChar c
if c == 'x'
then return ()
else mutGetChar
And at this point I won’t do anything more except explain why I kind of like
using continuations. It’s all about flexibility and composability. Say that
we have a library for dealing with events and a developer wants to use it to do
some processing of said events. I suspect that something that allows writing
code like
runContT (setupWorld >>= receiveEvent handleEvent) postProcessWorld
is to prefer over a set of straight-forward functions, some which take
“callbacks”. But hey! I might be wrong
I’m planning to see for myself!