IO

ContT

yield

> import Graphics.UI.GLUT > import Control.Monad.Cont

> display :: GLdouble -> IO () > display y = do > clear [ColorBuffer] > > renderPrimitive LineStrip $ do > vertex (Vertex2 (-1) (-y)) > vertex (Vertex2 1 y) > swapBuffers > postRedisplay Nothing

> main = do > (progname, _) <- getArgsAndInitialize > initialWindowSize $= Size 500 500 > initialDisplayMode $= [DoubleBuffered, RGBMode] > createWindow "Bounce!" > matrixMode $= Modelview 0 > loadIdentity > matrixMode $= Projection > loadIdentity > ortho (-1) 1 (-1) 1 (-1) 1

> imperative > mainLoop

liftIO

> imperative = flip runContT return $ do > liftIO $ print "Start!" > forever $ do > forM_ [-1, -0.992 .. 1.0] $ \y -> do > render $ display y > yield > liftIO $ print "Bounce!" > forM_ [-1, -0.992 .. 1.0] $ \y -> do > render $ display (-y) > yield > liftIO $ print "Bounce!" > yield

render

render

> render f = liftIO $ displayCallback $= f

yield

yield

ContT IO

ContT IO a

a

yield

ContT IO ()

()

ContT

newtype ContT r m a = Cont { runContT :: (a -> m r) -> m r }

r

r

()

yield

(() -> IO ()) -> IO ()

idleCallback

yield

yield = ContT $ \f -> ...

f

() -> IO ()

()

IO ()

idleCallback

> yield = ContT $ \f -> idleCallback $= Just (f () )

yield

IORef

It's taken for granted by many people that Haskell and static types are incompatible with prototyping and quick-and-dirty hacks.I wanted to put together some OpenGL code that had a script for how a bunch of graphics should be displayed. It was essentially an imperative specfification for my program. For quick and dirty hacks, GLUT is tolerable. But even when programming in C/C++, it's not supportive of programming in a straightforward imperative style because it uses inversion of control. Many graphics applications are written in a state machine style where the state machine gets to tick once each time there is an event callback. This really doesn't fit the imperative script style.But it is possible to reinvert inversion of control in any language that supports continuations. And that includes languages like Python that support linear continuations in the form of generators. But I'm using Haskell here.Continuations reify the remainder of a computation. Or in more down to earth language: they allow you to grab the stuff you're about to do as a function, put it on ice for a while, and then carry on doing it later. So imagine we had a block of imperative code and that we'd like, at each GLUT callback, to make some progress through this block. We can use continuations like this: each time we want to yield control back to the main loop we simply grab the remainder or our 'script' as a continuation and make it the callback to be executed next time GLUT is ready.The slight wrinkle is that OpenGL/GLUT calls use IO. To combineand continuations we need themonad transformer.I'll do everything except thefunction first and get back to that at the end.Some standard library imports:Some simple code to draw a line from left to right:Some standard OpenGL/GLUT setup:Our script is called before the main loop.And now comes the actual script. Apart from thecalls this should be almost as easy to read as BASIC programming from the days of yore:The first thing to note is thatdoesn't actually do any rendering. At the end of the day we can't tell GLUT when to render, it only calls you. So insteadtells GLUT what to do next time it's in the mood for a bit of rendering:That leaves one thing to explain:. It needs to grab the remainder of the script and package it up in a form suitable for installation as an idle callback. But there's a catch: continuations are notorious for making your head explode. If you're throwing together a quick and dirty hack, that's the last thing you need. Here's where static types come to the rescue. As Conor McBride points out, we want to just do the easy thing and follow gravity downwards.So first we try to guess the type of. We know we're working with themonad. So its type is going to befor some. There's no particular type of data we want to get out of, it's just a thing we want executed. So we can guess the type is, the typebeing the generic filler type when we don't actually have any data.Let's look at the definition ofThe typeis the final return type from our continuation. We're not interested in a return value, we just want to *do* stuff. So we expectto beas well.Somust essentially be of typeSo we want to concoct something of this type using GLUT'sfunction. Asmust take a function as argument it must look something like:We know thatis of type. So there's only one thing we can do with it: apply it to. That gives us something of type. That's precisely the type of GLUT's. So we put it all together:The code now works. I didn't have to spend even a moment thinking about the meaning of a continuation. Implementingwas about as hard as putting together a jigsaw puzzle with three pieces. There's only so many ways you can put the pieces together.And that's a simple example of why I often like to write quick-and-dirty code with a statically typed language.(Oh, and I'm not trying to take part in a war. I like to prototype in many different languages, some of which are dynamically typed.)PS Note also that the above code illustrates one way to avoids in GLUT code.