pretty package:prettyprinter

>>> pretty 1 <+> pretty "hello" <+> pretty 1.234
1 hello 1.234
>>> pretty 1 <+> pretty "hello" <+> pretty 1.234
1 hello 1.234
Overloaded conversion to Doc. Laws:
  1. output should be pretty. :-)
A modern, easy to use, well-documented, extensible pretty-printer. A modern, easy to use, well-documented, extensible pretty-printer. For more see README.md
prettyList is only used to define the instance Pretty a => Pretty [a]. In normal circumstances only the pretty function is used.
>>> prettyList [1, 23, 456]
[1, 23, 456]

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:
  1. Just below is some general information about the library.
  2. The actual library with extensive documentation and examples
  3. 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?
This is the default layout algorithm, and it is used by show, putDoc and hPutDoc. layoutPretty commits to rendering something in a certain way if the next element fits the layout constraints; in other words, it has one SimpleDocStream element lookahead when rendering. Consider using the smarter, but a bit less performant, layoutSmart algorithm if the results seem to run off to the right before having lots of line breaks.