[Haskell] Monadic pretty-printing

Tomasz Zielonka t.zielonka at students.mimuw.edu.pl
Sat Sep 25 08:19:05 EDT 2004


Hello!

In my master's thesis I am dealing with a complicated case of pretty
printing / unparsing. There are side effects, context dependency,
unparsing to two different languages (two SQL dialects). To manage
this complexity, I use a couple of ,,design patterns''.

One of them is about giving a monadic interface to pretty printing
library, to allow mixing it easily with various effects (IO, Reader,
Cont).

After some fiddling with a hand written pretty-printing monad, I
realized that I can just use a Writer monad. HughesPJ.Doc can be
trivially made an instance of Monoid - AFAIK all the needed laws hold.
The only problem is to decide which one of document composition
operators should be used as mappend. In my case I want <+> by default,
but I arranged things so that I can have several different versions of
Doc (newtyped).

As I needed many other pretty-printing combinators beside <+>/mappend,
I created a library of monadic pretty-printing combinators for
separating (eg. with commas), vertical composition, nesting, putting in
parentheses, <> composition, etc.  In some cases it was necessary to use
MonadWriter's "censor" method, but nothing more. I think it is quite
elegant.

The code written using this technique is also quite clear. Perhaps it
would be worse, if it wasn't possible to choose a composition operator
for Monoid doing the proper thing in 90% of cases. Below is a fragment
of code that handles expression unparsing.

    unparse (Expr_BinaryOp op e1 e2) = parens $ do
        unparse e1
        unparse op
        unparse e2

    unparse (Expr_UnaryOp UnaryOp_Not (Expr_IsNull e)) = parens $ do
        unparse e
        kw IS
        kw NOT
        kw NULL

    unparse (Expr_UnaryOp op e) = parens $ do
        unparse op
        unparse e

    unparse (Expr_Constant cons) = do
        unparse cons

    unparse (Expr_In e inSpec) = parens $ do
        unparse e
        kw IN
        oneline (unparse inSpec)

    unparse (Expr_Between e1 e2 e3) = parens $ do
        unparse e1
        kw BETWEEN
        unparse e2
        kw AND
        unparse e3

    unparse (Expr_Cast e datatype) = do
        kw CAST `catThen` parens (do
            unparse e
            kw AS
            unparse datatype)

    unparse (Expr_IsNull e) = parens $ do
        unparse e
        kw IS
        kw NULL

    ...

As you can see, sometimes there is a need to use a different composition
operator (catThen).

I think that this technique, simple as it is, can be very useful for
more complicated programs, especially if pretty-printing has to be
interleaved with some computational effects. It also helps to achieve
good modularity, by "dispatching on the monad type", which helps me to
handle two different dialects of SQL with quite good code reuse.


But getting to the point... I wonder if such a pattern was used or
proposed before. I tried to find some mention of it, but with no luck
(or luck :). Most examples of Writer monad use are about logging, etc.

If there are no protests, I am going to claim I was the first to use
such a technique. It may help me to rescue my poor master's thesis ;)

Best regards,
Tom

-- 
.signature: Too many levels of symbolic links


More information about the Haskell mailing list