Today's posting is by a guest author, Jon Dowland.


I wanted to refresh my Haskell skills recently so I picked up an old idea I'd had of writing a successor to WadC in Haskell. WadC is a LOGO-like language for making 2D drawings (Doom maps). You write a sequence of instructions that move a "pen" around and draw lines, place things, etc. It looks a bit like this:

...
movestep(32,32)
thing
straight(256)
rotright
straight(512)
rotright
...

The driving force behind for writing a successor to WadC is to work around one of its most serious limitations: you do not have direct access to the data structure that you are building. Once you place a line, it's defined and you can't change it, or even query existing drawn lines to find out if it exists. The appeal of using Haskell would be to give you access to the data structure that's "behind the scenes" as well as Haskell's rich features and libraries.

WadC's syntax, like LOGO, resembles an ordered sequence of operations that manipulate some hidden state relating to the Pen: its orientation, whether its on the "paper" or not, what colour line is being drawn, etc. I thought this would look particularly nice in Haskell's "do notation" syntax, e.g.:

blah = do
    down
    straight 64
    rotright
    straight 128
    rotright
...

Ignoring the syntax issue for a minute and thinking just about types, the most natural type to me for the atomic operations would be something like Context -> Context: simply put, each function would receive the current state of the world, and return a new state. For example Context could be something like

data Context = Context { location :: Point          -- (x,y)
                       , orientation :: Orientation -- N/E/S/W
                       , linedefs :: [Line]         -- Line = from/to (x,y)/(x,y)
                       , penstate :: PenState       -- up or down
                       ...

An example of a simple low-level function that would work with this:

    down c = c { penstate = Down }

The immediate advantage here over WadC is that the functions have access to all of the existing context: they could not only append lines, say, but replace any existing lines too. I was envisaging that you might, for example, adjust all drawn lines as if you were using a completely different geometric model to get some weird effects (such as one axis being warped towards a single point). Or perhaps you would super-impose two separate drawings and have fine control over how overlapping regions have their properties combined (one overrides the other, or blend them, etc.)

The problem is in uniting the simple types with using do-notation, which requires the routines to be "wrapped up" in Monads. I got a prototype working by writing a custom Monad, but it required me to either modify the type of each of the low-level functions, or wrap each invocation of them inside the do-block. If you imagine the wrap function is called wadL, that means either

down = wadL down' where
    down' c = c { penstate = Down }

or

blah = do
    wadL $ down
    wadL $ straight 64
    wadL $ rotright

Both approaches are pretty ugly. The former gets ugly fast when the functions concerned are more complicated in the first place; the latter pretty much throws away the niceness of using do-notation at all.

An alternative solution is one that the Diagrams package uses: define a new infix operator which is just function composition (.) backwards:

down        &
straight 64 &
rotright

(Diagrams uses # but I prefer &)

By playing around with this I've achieved my original goal of refreshing my Haskell skills. Unfortunately I don't have the time or inclination to continue with this side-project right now so I haven't published a working Haskell-powered Doom map editor.

If I return to this again, I might explore writing a completely distinct language (like WadC is) with the compiler/interpreter likely written using Haskell.