Monadic pretty-printing
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
participants (1)
-
Tomasz Zielonka