> module UserManPP where
> import UU_Pretty
>
> infixr 3 >#<, >>#<<
> infixr 1 >^<, >>^<<

 

UU_Pretty User Manual

Pablo R. Azero

Department of Computer Science

Utrecht University

January, 1999  

 

This file is a literate Haskell script intended as a short User Manual for the UU_Pretty library. Knowledge of the language Haskell is assumed. It is also assumed that the reader has access to a Haskell compiler or (better) an interpreter while reading this document. Along the text the reader is presented with examples that he/she can later on find in the UU_Pretty_ext library. Thus it is also an introduction to the use of UU_Pretty_ext library.

The manual is organized as follows. The first section treats the single layout combinators, almost the same to the ones found in version 1.x of the library (the reader familiar with them can skip most of the material in this section, but not all). The second section explains how to program combinators of multiple layouts. The Appendix contains the interface of UU_Pretty including the type signature of all the functions in the module interface.

Single Layout Combinators

The basic building blocks for pretty-printing are PP documents (of type PP_Doc). You obtain PP documents either by pretty-printing your Haskell structures using the primitive text or the members of the class PP or by combining PP documents with the pretty-printing operators.

Interface

Comments

empty

The empty PP document and null element for the operations beside and above

text s

Converts the string s into a PP document

render ppd pw

Prints the PP document ppd (to IO ()) in a page pw (>=0) wide

We are ready to try out the first examples:

> tenplus = "01234567890 ha!"
> t_text  = render (text tenplus) 15

whose output is 0123456789 ha!. But:

> t_error = render (text tenplus) 10

will result in <************>, because the page is not wide enough. The number of characters of the output line is the actual width of the input string.

PP

A class defining the functions pp and ppList whose main task is to convert any datatype D to a PP document . The conversion is done using the text printer and thus an instance of Show for D is required. ppList default definition places all the elements of the list horizontally

disp ppd pw

Prints the PP document ppd (to ShowS) in a page pw (>=0) wide

Thus we try:

> ha     = "ha!"
> t_disp = putStr . disp (pp tenplus) 15 . disp (text ha) 10 $ ""

to get 01234567890 ha!ha!.

And if we include the instance declaration:

> instance PP Int where
>   pp = text . show

we can:

> t_int = render (pp (30::Int)) 10

to get 30.

It is useful to have this image of a PP document in mind when you use the combinators:

                        width
                  v              v
             ^    |--------------|              
                  |              |                
          height  |              |           
                  |        |-----|                
             v    |--------|      <- last line

It also shows some attributes of a PP document, like its width or its height. In the next sections you will see that those attributes can be used to format flexible layouts, and they are of course used in the implementation of the library.

x >|< y

x beside y

x >-< y

x above y

Horizontal (beside) and vertical (above) composition are the principal combinators used to build more complex documents. For example:

> hello      = "hello"
> world      = "world!"
> h_beside_w = hello >|< world
> t_beside   = render h_beside_w 20

results in helloworld!, that is not very nice. We need a function that takes two PP documents and composes them horizontally with a space in between. We call this operator "beside space" (>#<) and we define it as follows:

> (>#<) :: (PP a, PP b) => a -> b -> PP_Doc
> a >#< b = a >|< " " >|< b
>
> t_bsp   = render (hello >#< world) 20

gives now a nicer hello world!.

Note that the empty PP document is the only document with height 0. It's the only possibility to make it a zero for both operators beside and above. Thus, the empty string is not equivalent to an empty PP document!

With the help of beside, above and empty we can now generalize the beside and above operators to functions on a list of arguments:

> hlist, vlist :: PP a => [a] -> PP_Doc
> hlist = foldr (>|<) empty
> vlist = foldr (>-<) empty
>
> nums  = map pp [ (10::Int) .. 20 ]
> angle = render ( hlist nums  >-<  (vlist . tail $ nums) ) 30

Also keep in mind that a beside operation is not a horizontal concatenation of blocks if the blocks are higher than one line as illustrated in the next example:

> h_above_w = hello >-< world
> stairs
>   = render (hlist [ h_above_w, h_above_w, h_above_w, h_above_w ])

if we now type stairs 30 we get:

hello
world!hello
      world!hello
            world!hello
                  world!

We call this the stair effect. With the previous example we can also see the effect of recovery from a page width overflow when we issue stairs 17 to obtain:

hello
world!hello
      world!hello
            <****><***>
                  <****>

indent s ppd

Inserts s (integer) spaces in front of ppd, fails and gives an error message when s is negative

fill ppds

ppds is a list of PP documents, fill appends them horizontally towards the page width limit, if there are more elements that can fit in one line it continues on the next line. Requires that all elements have height one, otherwise an error message will be issued

Use fill with care and always as the last element in a horizontal sequence if you do not want surprises:

> tens      = map pp [(10::Int),100,1000,10000,100000,1000000]
> t_fill pw = render ( (indent 10 . fill $ tens) >|< hello) pw
>
> t_fill27  = t_fill 27
> t_fill28  = t_fill 28

t_fill28 results in

          10100100010000
          1000001000000hello

but t_fill27 fails

          10100100010000
          1000001000000<***>

fillblock n ppds

Same as fill above but n specifying a narrower width limit (n < page width). It fails when n is negative or n is larger than the global page width

> nlist   = map pp [(100::Int) .. 120 ]
> t_fillb = render ((indent 5 . fillblock 15 $ nlist) >|< tens) 40

vcenter ppds

Display the PP documents ppds vertically and centered relative to the maximum width of all the elements in ppds

> tens'   = let ttens :: [ Int ]
>               ttens =  [1,101,10001,1000001]
>           in  map pp (ttens ++ reverse ttens)
> diamond = render (indent 10 . vcenter $ tens') 30

 

Programming flexible layouts

To overcome the constraint of the page width limit we normally have in mind more than one possible layout. For instance if we want to print the expression HelloWorld!, we will use this horizontal layout, but in case no such wide space is available, we invent an alternative layout such as

Hello
World!

To specify alternative layouts we provide two combinators: dup and join.

Interface
Comments

x >//< y

x dup y, the two alternative PP documents x and y are kept in parallel

join f

Merges the alternatives into one PP document

element_h1 ppd

fail if the PP document ppd has a layout higher than one line

> hv_helloworld = join (hello >|< world >//< hello >-< world)
> hor_hw        = render hv_helloworld 20

You can not render or display a PP document after a dup operation. It is a situation where to alternative layouts are kept, but no choice has been made about which one is preferable. Only after a join the actual choice is made. Most of the times this temporal suspension is not needed, thus to every dup will be immediately followed by a join. This can be made explicit defining the operator choice (>^<):

> (>^<)   :: (PP a, PP b) => a -> b -> PP_Doc
> a >^< b = join (a >//< b)

How does the join selects which alternative is better? It will take the widest one (an consequently less higher) as long as it fits in the horizontal space left (compare the result of hor_hw vs. ver_hw ).

> ver_hw   = render (hello >|< world >^< hello >-< world) 10

We can generalize a little bit the previous definition:

> hv_ab a b = element_h1 (a >|< b) >^< a >-< b

Now we not only can place horizontally or vertically any pair of PP documents, but also we have prevented that a horizontal layout has a layout higher than one line (use of element_h1). This prevents the stair effect discused above.

If we take a careful look at the definition of ver_hw (or hv_ab), we are using the name hello twice. In normal Haskell expressions this is not a problem, we say that we are sharing the value that the name hello stands for. In the case of our combinators all PP documents are functions depending on a value (the page width). Both name references are thus different (each one is called with a different page width value), resulting in duplication of efforts. We say in this case that sharing is lost. We refer to the interested reader to [SAS 98] and [SA 99] for an in-depth discussion of the problem.

We will not use names but instead anonymous placeholders called pars. The resulting expressions will be called PP expressions (of type PP_Exp). We also need a binding mechanism to associate those placeholders to actual PP documents, and for that we use the operator apply.

par

Placeholder for a PP document in a PP expression

pe >>$< ppds

pe apply ppds, where the latter contains a list of PP placetaker documents that will replace the placeholders in the PP expression pe. The order of association is from left to right in the order they appear (in the PP expression and in the list)

The simplest PP expression and binding that we can have is:

> t_parexp = render (par >>$< [pp hello]) 20

that acts as replacing the placeholder for the PP document pp hello.

Things start to get complicated here but it is worth the flexibility we gain to express alternative layouts.

To express arbitrary complicated par expressions we provide the following set of combinators, that is esentially the same than the one provided for PP documents.

eindent i pe

inserts i spaces in front of pe

x >>|<< y

PP expression x beside PP expression y

x >>-<< y

PP expression x above PP expression y

x >>//<< y

PP expression x dup PP expression y, both alternatives are kept in parallel, but in order to use apply, they should contain the same number or par elements (par balancing)

ejoin pe

Merges the alternatives into one PP expression

renderAll ppd pw

Prints all the possible layouts of the PP document ppd (to IO ()) taking pw (>=0) as page width

eelement_h1 pe

fail if the PP expression pe has a layout higher than one line

For an example, let us return to the function ver_vh above. We can now express it in terms of a PP expression:

> a >>^<< b = ejoin (a >>//<< b)
> hv2 a b   =     eelement_h1 (par >>|<< par)
>           >>^<<              par >>-<< par
>           >>$<              [a   ,     b  ]
>
> t_hv2     = renderAll ( hv2 h_beside_w h_beside_w ) 25

The par expression in hv2 has at both sides of the choice two placeholders, it is thus par balanced. Furthermore the list of PP documents has length 2 matching the number of placeholders, thus the PP document is correct. Any violation of par balancing or matching number of placeholders vs. placetakers will result in an error message.

Convention: double arrows on operators mean that a PP expression is expected as operand, and single arrows that a PP document is expected. For intance, the apply operator (>>$<) expects to the left a par expression and to the right a PP document (actually to be precise, a list of PP documents).

Notice also that prefix operators for par expressions have been prepended with a "e" to differentiate them from their counterparts for PP documents.

Note also that in hv2 we have prevented an stair effect of the horizontal layout with the use of eelement_h1 applied to the beside PP expression.

The generalization of hv2 to any number of PP documents is not straightforward. First because the repeated use of hv can lead to the stair effect:

> hhvv   = hv2 h_beside_w h_beside_w
> hv_err = render (foldr hv2 empty [hhvv,hhvv,hhvv]) 45

will give:

helloworld!helloworld!
helloworld!helloworld!helloworld!helloworld!

that does not satisfy our expectation. We would like a horizonal layout or a vertical one of all the elements.

> hv :: PP a => [a] -> PP_Doc
> hv = join . foldr onehv (empty >//< empty) .map pp
>   where onehv p ps =      eelement_h1 par >>|<< fpar
>                    >>//<<             par >>-<< spar
>                    >>$<              [p     ,   ps  ]
> hv_good = render (hv [hhvv,hhvv,hhvv]) 45

The function onehv above is almost our previous hv2, except that we do not join the result, and this is why we need the fpar and spar. The result of onehv is a paired PP document. Because we are trying to keep computations separated and repetitive applications of onehv require as second argument a pair of PP documents, we need to address the placeholder for ps separately: fpar for its first component and spar for the second.

fpar

placeholder for the first element of a paired PP expression

spar

placeholder for the second element of a paired PP expression

Remember that fpar and spar together form one placeholder for a paired PP expression.

The reader can find two more generalizations of hv for lists in the library UU_Pretty_ext, the combinators hv_sp (same as hv with spaces separating the elements) and pp_block (a block structure with open and close keywords and a separator).

pe >>$<< pes

apply lifted to the PP expression level, meaning that pe is a PP expression and pes a list of PP expressions replacing the placeholders of pe

c2e ppd

Takes a PP document ppd and lifts it to a constant PP expression (thus it does not count as placeholder)

Before our last complicated example, let us invent a new combinator: beside space for PP expressions. For this we need the combinator c2e that converts a PP document into a PP expression.

> a >>#<< b = a >>|<< c2e " " >>|<< b

Now let us write a combinator for the expression if-then-else, and suppose we like three layouts for displaying it:

if condition then then-part else else-part

or:

if condition then then-part
             else else-part

or when we are really short of space:

if condition
then then-part
else else-part

A first approach would be:

> pp_ite' c t e

... put all horizontal if they can fit on the line ...

>   =  (     eelement_h1 (    par    >>#<<    par      >>#<<     par      )

... or the if ... and horizontally a vertical then-else ...

>      >>^<<             (    par    >>#<<    (par     >>-<<     par     ))

... or a total vertical layout.

>      >>^<<             (    par    >>-<<    par      >>-<<     par      )
>      >>$<              [ "if" >#< c  ,   "then" >#< t  ,   "else" >#< e ]
>      )

But we can improve it a little bit since the printing of the then and the else is almost an hv in PP expressions:

> pp_ite c t e
>   = (     eelement_h1 ( par >>#<< par >>#<< par )
>     >>^<<     (         par >>#<<      par
>               >>^<<     par >>-<<      par
>               >>$<<   [ par   ,   par >>-<< par ]
>               )
>     ) >>$< [ "if" >#< c, "then" >#< t, "else" >#< e ]

We can see that the PP expression is par balanced and that all placeholders have a corresponding placetaker. The PP expression:

par >>#<< par >>^<< par >>-<< par >>$<< [par, par >>-<< par]

is indeed correct, taking two placeholders to the left of the apply and a list of two placetakers to the right. But the placetakers actually stand for three placeholders too, just what we need to fit the outermost choice.

We can try

> t_ite = pp_ite "a > b"
>                "\"max is a\""
>                "\"max is b\""

with page widths 10, 15, 24 and 40.

invisible ppd

Makes the formatted element invisible (all its attributes are forgotten in order to always succeed, even if there is no space left!)

In exceptional cases we want to escape to the page width limit, for example when you want to generate additional tags for markup languages such as LaTeX or HTML:

> res_word rw = invisible (pp "{\\bf ") >|< rw >|< invisible (pp "}")

and try render (res_word "let") 3.

 

Appendix: Interface of UU_Pretty

Binary operators: associativity and priority

infixr 3 >|< , >>|<<
infixr 2 >-< , >>-<<
infixr 1 >//<, >>//<<
infixr 0 >>$<, >>$<<

 

Datatypes and classes

class Show a => PP a where
  pp     :: a   -> PP_Doc
  ppList :: [a] -> PP_Doc

PP_Doc : the type of PP documents
PP_Exp : the type of PP expressions

 

Single layout combinators

empty        ::                 PP_Doc
text         ::                 String -> PP_Doc
indent       :: PP a         => Int    -> a      -> PP_Doc
(>|<), (>-<) :: (PP a, PP b) => a      -> b      -> PP_Doc
fill         :: PP a         => [a]    -> PP_Doc
fillblock    :: PP a         => Int    -> [a]    -> PP_Doc
element_h1   ::                 PP_Doc -> PP_Doc
vcenter      :: PP a         => [a]    -> PP_Doc
invisible    ::                 PP_Doc -> PP_Doc

 

Multiple layout combinators

(>//<)      :: (PP a, PP b) => b      -> a        -> PP_Doc
join        ::                 PP_Doc -> PP_Doc
par         ::                 PP_Exp
(>>$<)      :: PP a         => PP_Exp -> [a]      -> PP_Doc
eindent     ::                 Int    -> PP_Exp   -> PP_Exp
(>>|<<),
 (>>-<<),
 (>>//<<)   ::                 PP_Exp -> PP_Exp   -> PP_Exp
ejoin       ::                 PP_Exp -> PP_Exp
fpar        ::                 PP_Exp
spar        ::                 PP_Exp
(>>$<<)     ::                 PP_Exp -> [PP_Exp] -> PP_Exp
c2e         :: PP a         => a      -> PP_Exp
eelement_h1 ::                 PP_Exp -> PP_Exp

 

Displaying the result

render, renderAll :: PP_Doc -> Int -> IO ()
disp              :: PP_Doc -> Int -> ShowS

 

References

  1. [SAS 98] Design and Implementation of Combinator Languages. S. Doaitse Swierstra, Pablo R. Azero and Joao Saraiva Third Summer School on Advanced Functional Programming, Braga, Portugal. September, 1998. (pdf)
  2. [SA 99] How to Share a Call. S. Doaitse Swierstra and Pablo R. Azero. (in preparation)

We would like to hear your comments and suggestions. Return to the main page of the pretty-printing library.