Overview
This module defines a prettyprinter to format text in a flexible and
convenient way. The idea is to combine a
Document out of many
small components, then using a layouter to convert it to an easily
renderable
SimpleDocStream, which can then be rendered to a
variety of formats, for example plain
Text.
The documentation consists of several parts:
- Just below is some general information about the library.
- The actual library with extensive documentation and examples
- Migration guide for users familiar with (ansi-)wl-pprint
Starting out
As a reading list for starters, some of the most commonly used
functions in this module include
<>,
hsep,
<+>,
vsep,
align,
hang. These cover
many use cases already, and many other functions are variations or
combinations of these.
Simple example
Let’s prettyprint a simple Haskell type definition. First, intersperse
-> and add a leading
::,
>>> :{
>>> prettyprintType :: [Doc x] -> Doc x
>>> prettyprintType = align . sep . zipWith (<+>) ("::" : repeat "->")
>>> :}
The
sep function is one way of concatenating documents, there
are multiple others, e.g.
vsep,
cat and
fillSep.
In our case,
sep space-separates all entries if there is space,
and newlines if the remaining line is too short.
Second, prepend the name to the type,
>>> let prettyprintDeclaration n tys = pretty n <+> prettyprintType tys
Now we can define a document that contains some type signature:
>>> let doc = prettyprintDeclaration "example" ["Int", "Bool", "Char", "IO ()"]
This document can now be printed, and it automatically adapts to
available space. If the page is wide enough (80 characters in this
case), the definitions are space-separated,
>>> putDocW 80 doc
example :: Int -> Bool -> Char -> IO ()
If we narrow the page width to only 20 characters, the
same
document renders vertically aligned:
>>> putDocW 20 doc
example :: Int
-> Bool
-> Char
-> IO ()
Speaking of alignment, had we not used
align, the
-> would be at the beginning of each line, and not beneath
the
::.
The
putDocW renderer used here is from
Prettyprinter.Util.
General workflow
╔══════════╗
║ ║ ╭────────────────────╮
║ ║ │ vsep, pretty, <+>, │
║ ║ │ nest, align, … │
║ ║ ╰─────────┬──────────╯
║ ║ │
║ Create ║ │
║ ║ │
║ ║ ▽
║ ║ ╭───────────────────╮
║ ║ │ Doc │
╠══════════╣ │ (rich document) │
║ ║ ╰─────────┬─────────╯
║ ║ │
║ ║ │ Layout algorithms
║ Layout ║ │ e.g. layoutPretty
║ ║ ▽
║ ║ ╭───────────────────╮
║ ║ │ SimpleDocStream │
╠══════════╣ │ (simple document) │
║ ║ ╰─────────┬─────────╯
║ ║ │
║ ║ ├─────────────────────────────╮
║ ║ │ │ treeForm
║ ║ │ ▽
║ ║ │ ╭───────────────╮
║ ║ │ │ SimpleDocTree │
║ Render ║ │ ╰───────┬───────╯
║ ║ │ │
║ ║ ╭───────────────────┼─────────────────╮ ╭────────┴────────╮
║ ║ │ │ │ │ │
║ ║ ▽ ▽ ▽ ▽ ▽
║ ║ ╭───────────────╮ ╭───────────────╮ ╭───────────────╮ ╭───────────────╮
║ ║ │ ANSI terminal │ │ Plain Text │ │ other/custom │ │ HTML │
║ ║ ╰───────────────╯ ╰───────────────╯ ╰───────────────╯ ╰───────────────╯
║ ║
╚══════════╝
How the layout works
There are two key concepts to laying a document out: the available
width, and
grouping.
Available width
The page has a certain maximum width, which the layouter tries to not
exceed, by inserting line breaks where possible. The functions given
in this module make it fairly straightforward to specify where, and
under what circumstances, such a line break may be inserted by the
layouter, for example via the
sep function.
There is also the concept of
ribbon width. The ribbon is the
part of a line that is printed, i.e. the line length without the
leading indentation. The layouters take a ribbon fraction argument,
which specifies how much of a line should be filled before trying to
break it up. A ribbon width of 0.5 in a document of width 80 will
result in the layouter to try to not exceed
0.5*80 = 40
(ignoring current indentation depth).
Grouping
A document can be
grouped, which tells the layouter that it
should attempt to collapse it to a single line. If the result does not
fit within the constraints (given by page and ribbon widths), the
document is rendered unaltered. This allows fallback definitions, so
that we get nice results even when the original document would exceed
the layout constraints.
Things the prettyprinter cannot do
Due to how the Wadler/Leijen algorithm is designed, a couple of things
are unsupported right now, with a high possibility of having no
sensible implementation without significantly changing the layout
algorithm. In particular, this includes
- Leading symbols instead of just spaces for indentation, as used by
the Linux tree tool for example
- Multi-column layouts, in particular tables with multiple cells of
equal width adjacent to each other
Some helpful tips
Which kind of annotation should I use?
Summary: Use semantic annotations for
Doc, and
after layouting map to backend-specific ones.
For example, suppose you want to prettyprint some programming language
code. If you want keywords to be red, you should annotate the
Doc with a type that has a
Keyword field
(without any notion of color), and then after layouting convert the
annotations to map
Keyword to e.g.
Red (using
reAnnotateS). The
alternative that I
do not recommend is directly annotating the
Doc with
Red.
While both versions would superficially work equally well and would
create identical output, the recommended way has two significant
advantages: modularity and extensibility.
Modularity: To change the color of keywords later, you have to
touch one point, namely the mapping in
reAnnotateS,
where
Keyword is mapped to
Red. If you have
'annotate Red …' everywher, you’ll have to do a full text
replacement, producing a large diff and touching lots of places for a
very small change.
Extensibility: Adding a different backend in the recommended
version is simply adding another
reAnnotateS to
convert the
Doc annotation to something else. On the
other hand, if you have
Red as an annotation in the
Doc already and the other backend does not support
anything red (think of plain text or a website where red doesn’t work
well with the rest of the style), you’ll have to worry about what to
map »redness« to, which has no canonical answer. Should it be omitted?
What does »red« mean anyway – maybe keywords and variables are red,
and you want to change only the color of variables?