Aug 21 2008
Lord of the Flies as a window onto monadic IO
I like thinking about monadic structures in Haskell as universes — a different universe for each one — a universe of non-determinism, a universe of mutable state, and so on.
The IO monad, by this analogy, is our universe. Whatever happens in the IO monad actually happens. It’s necessary in Haskell to enforce an ordering on actions which affect our universe, because in a lazy language you don’t have much of an explicit ordering. So functions in the IO monad are allowed to change the world, and only one can do this at a time. It’s a bit like a State monad, except nobody actually bothers passing round the entire state of the universe from one function to another because that would be silly. As far as I can tell, it’s normally an access token. Like a conch, I suppose.
Sometimes, though, you might want to play out a hypothetical situation. “I don’t have the conch right now, but if I did this is what I would say…” This tricky hypothetical universe will then vanish when you’ve finished your little daydream. The Haskell way of doing this is with unsafePerformIO. It lets you make your own conch (well, maybe just a barnacle?), which can be very useful and very dangerous. You may do things which are very useful, but you might also destabilise the state of the world, bringing chaos and so on.
I recently had cause to use it in Brent Yorgey’s Diagrams package. It’s a lovely EDSL for creating simple diagrams, which is built on the Cairo library. The one thing it didn’t support was text — the very thing I wanted to use.
Brent’s library has an interface for all the shapes you might need to draw. Each shape has to supply functionality to (a) return its own size and (b) draw itself. Part B was easy enough, since all that is basically done by Cairo library. I was just joining it in the right place. The size part was much harder, since the size of a label is dependent on the typeface, font size and the characters used. You can’t really tell ahead of time what it will be.
My first thought was to let the user add a bounding box which the text would fit in, but this is almost totally unusable in practise. The proportion of width to height changes depending on the text and the font in use (as well as the size, if you assume hinting). You’d have to be clairvoyant to know exactly the width and height required so that the text wasn’t squished sideways or flattened at awkward proportions. And I was assuming that people would want text that was readable without much effort.
My next attempt was to assume the bounding box was only the upper limit, but if the text had to be shorter in either dimension to look good then it would be. This works reasonably well for a single chunk of text, but there’s no consistency. The only way to ensure consistent sizes across multiple labels is if they all say the same text in the same font. This is just as unreasonable as the requirement that the user by clairvoyant.
My problem — the real problem that was demanding all these awkward workarounds — was that the shapeSize function was pure and textExtents was decidedly not.
class ShapeClass s where shapeSize :: s -> (Double,Double) renderShape :: s -> DiaRenderM () textExtents :: String -> Render TextExtents fontExtents :: Render FontExtents
In fact, the monad Render is a huge stack of monad transformers
The two are not even slightly compatible in most situations.
In the end I bit the bullet, and used unsafePerformIO. I am pretty sure that it doesn’t affect the world. In effect, I am saying to the Cairo library:
“if I were to give you this string BLAH, what would you say the bounding box would be around it?”
“Oh, I’d say about X and Y. Why do you ask?”
“Oh, no reason…”
I think I’m safe.