[More flesh for chapter 6. Bryan O'Sullivan **20080311063900] { hunk ./en/ch06-library.xml 778 + + Infix use of constructors + + We've used infix notation to represent the + Concat and Union constructors in the + final two patterns of our &case; expression. This has no + effect on the meaning of our code. Just like using a + constructor or function infix in an application, it's merely a + small change for readability. + + hunk ./en/ch06-library.xml 911 - Foo. + While our compact function is useful + for machine-to-machine communication, its result is not always + easy for a human to follow: there's very little information on + each line. To generate more readable output, we'll write + another function, pretty. Compared to + compact, pretty + takes one extra argument: the maximum width of a line, in + columns. (We're assuming that our typeface is of fixed + width.) + + &Prettify.hs:pretty.type; + + To be more precise, this Int parameter + controls the behaviour of pretty when it + encounters a softline. Only at a + softline does pretty + have the option of either continuing the current line or + beginning a new line. Elsewhere, we must strictly follow the + directives set out by the person using our pretty printing + combinators. + + Here's the core of our implementation + + &Prettify.hs:pretty; + + Our best helper function takes two + arguments: the number of columns emitted so far on the current + line, and the list of remaining Doc values to + process. + + In the simple cases, best updates the + col variable in straightforward ways as it + consumes the input. Even the Concat case is + obvious: we push the two concatenated components onto our + stack/list, and don't touch col. + + The interesting case is concerned with the + Union constructor. Recall that we applied + flatten to the left element, and did + nothing to the right. Also, remember that + flatten replaces newlines with spaces. + Therefore, our job is to see which (ir either) of the two + layouts, the flattened one or the + original, will fit into our width + restriction. + + &Prettify.hs:nicest; + + To do this, we write a small helper that determines + whether a single line of a rendered Doc value + will fit into a given number of columns. + + &Prettify.hs:fits; + + + + + Following the pretty printer + + In order to understand how this code works, let's first + consider a very simple Doc value. + + &prettify.ghci:simple; + + We'll call pretty 2 on this value. + When we first apply best, the value of + col is zero. It matches the Concat + case, pushes the values Union (Char ' ') Line and + Char 'a' onto the stack, and applies itself + recursively. In the recursive application, it matches on + Union (Char ' ') Line. + + At this point, we're going to ignore Haskell's usual order + of evaluation. This keeps our explanation of what's going on + simple, without changing the end result. We now have two + subexpressions, best 0 [Char ' ', Char 'a'] and + best 0 [Line, Char 'a']. The first evaluates to + " a", and the second to "\na". We + then substitute these into the outer expression to give + nicest 0 " a" "\na". + + To figure out what the result of + nicest is here, we do a little + substitution. The values of width and + col are 0 and 2, respectively, so + least is 0, and width - least + is 2. We quickly evaluate 2 `fits` " a" in + &ghci;. + + &prettify.ghci:fits; + + Since this evaluates to True, the result of + nicest here is " a". + + If we apply our pretty function to + the same JSON data as earlier, we can see that it produces + different output depending on the width that we give + it. + + &prettyjson.ghci:pretty; + + + + + Exercises + + We can make a number of interesting improvements to our + pretty printer. + + + + + Our pretty printer does not take + nesting into account. Whenever we + open parentheses, braces, or brackets, any lines that + follow should be indented so that they are aligned with + the opening character until a matching closing character + is encountered. + + Add support for nesting, with a controllable amount + of indentation. + + &Prettify.hs:nest; + + + + + + + + Practical pointers, and further reading + + Blah. hunk ./examples/ch06/Prettify.hs 91 -flatten Line = Text " " +flatten Line = Char ' ' hunk ./examples/ch06/Prettify.hs 117 -{-- snippet pretty --} +{-- snippet pretty.type --} hunk ./examples/ch06/Prettify.hs 119 +{-- /snippet pretty.type --} + +{-- snippet pretty --} hunk ./examples/ch06/Prettify.hs 128 - Line -> '\n' : best col ds + Line -> '\n' : best 0 ds hunk ./examples/ch06/Prettify.hs 136 - nicest col a b | min width col `fits` a = a - | otherwise = b + nicest col a b | (width - least) `fits` a = a + | otherwise = b + where least = min width col hunk ./examples/ch06/Prettify.hs 141 -w `fits` x | w < 0 = False +{-- snippet fits --} +fits :: Int -> String -> Bool +w `fits` _ | w < 0 = False hunk ./examples/ch06/Prettify.hs 147 +{-- /snippet fits --} + +{-- snippet nest --} +nest :: Int -> Doc -> Doc +{-- /snippet nest --} +nest = undefined hunk ./examples/ch06/PrettyJSON.hs 58 -string = enclose '\"' '\"' . hcat . map oneChar +string = enclose '"' '"' . hcat . map oneChar hunk ./examples/ch06/prettify.ghci 8 +--# simple +empty char 'a' + +--# fits +2 `fits` " a" + +--# pretty +pretty 2 (empty char 'a') +pretty 1 (empty char 'a') + hunk ./examples/ch06/prettyjson.ghci 26 +--# pretty +putStrLn (pretty 10 value) +putStrLn (pretty 20 value) +putStrLn (pretty 30 value) + }