Paiges

Paiges is an implementation of Philip Wadler's Prettier Printer.

Getting started

To get started, you'll want to import Doc. This is the basis for Paiges, and is likely the only type you'll need to work with:

import org.typelevel.paiges.Doc

Creating documents

Many documents are defined in terms of other documents, but to get started you'll need to create documents to represent chunks of text. Here are three good ways to do it.

// unbroken text from a String
val cat = Doc.text("cat")
// cat: Doc = Text(str = "cat")

// same as text, but using .toString first
val pair = Doc.str((1, 2))
// pair: Doc = Text(str = "(1,2)")

// allow breaking on word-boundaries
val doc = Doc.paragraph(
  """The basic tool for the manipulation of reality is the
  manipulation of words. If you can control the meaning of
  words, you can control the people who must use them.""")
// doc: Doc = Concat(
//   a = Text(str = "The"),
//   b = Concat(
//     a = Union(
//       a = Text(str = " "),
//       b = FlatAlt(default = Doc(...), whenFlat = Text(str = " "))
//     ),
//     b = Concat(
//       a = Text(str = "basic"),
//       b = Concat(
//         a = Union(
//           a = Text(str = " "),
//           b = FlatAlt(default = Doc(...), whenFlat = Text(str = " "))
//         ),
//         b = Concat(
//           a = Text(str = "tool"),
//           b = Concat(
//             a = Union(
//               a = Text(str = " "),
//               b = FlatAlt(default = Doc(...), whenFlat = Text(str = " "))
//             ),
//             b = Concat(
//               a = Text(str = "for"),
//               b = Concat(
//                 a = Union(
//                   a = Text(str = " "),
//                   b = FlatAlt(default = Doc(...), whenFlat = Text(str = " "))
//                 ),
//                 b = Concat(
//                   a = Text(str = "the"),
//                   b = Concat(
//                     a = Union(
//                       a = Text(str = " "),
//                       b = FlatAlt(
//                         default = Doc(...),
//                         whenFlat = Text(str = " ")
//                       )
//                     ),
//                     b = Concat(
//                       a = Text(str = "manipulation"),
//                       b = Concat(
//                         a = Union(
//                           a = Text(str = " "),
//                           b = FlatAlt(
//                             default = Doc(...),
//                             whenFlat = Text(str = " ")
//                           )
//                         ),
//                         b = Concat(
// ...

We can use a single line of text to contrast these methods:

val howl = "I saw the best minds of my generation destroyed by madness..."

The first two, Doc.text and Doc.str, create a fragment of text that will always render exactly as shown. This means that if you create a very long piece of text, it won't be wrapped to fit smaller widths.

Doc.text(howl).render(40)
// res0: String = "I saw the best minds of my generation destroyed by madness..."

By contrast, Doc.paragraph will add line breaks to make the document fit:

Doc.paragraph(howl).render(40)
// res1: String = """I saw the best minds of my generation
// destroyed by madness..."""

There are also some useful methods defined in the Doc companion:

Combining documents

Paiges' ability to vary rendering based on width comes from the different combinators it provides on Doc. In general, you'll want to build documents to represent discrete, atomic bits of text, and then use other combinators to build up larger documents.

This section gives a brief overview to some of the most useful combinators.

Concatenation

The most basic operations on documents involve concatenation: stitching documents together to create new documents. Since Doc is immutable, none of these methods modify documents directly. Instead, they return a new document which represents the concatenation. This immutability allows Paiges to render at different widths so efficiently.

Documents are concatenation with +. As a convenience, Paiges also provides +: and :+ to prepend or append a string.

val doc1 = Doc.text("my pet is a ") + Doc.text("cat")
// doc1: Doc = Concat(a = Text(str = "my pet is a "), b = Text(str = "cat"))
val doc2 = Doc.text("my pet is a ") :+ "cat"
// doc2: Doc = Concat(a = Text(str = "my pet is a "), b = Text(str = "cat"))
val doc3 = "my pet is a " +: Doc.text("cat")
// doc3: Doc = Concat(a = Text(str = "my pet is a "), b = Text(str = "cat"))

val doc4 = Doc.text("my pet is a " + "cat")
// doc4: Doc = Text(str = "my pet is a cat")

The first three documents are equivalent. However, the last is different: it performs a string concatenation before creating a single document, which will be less efficient. When using Doc, it's preferable to turn literal strings into documents before concatenating them.

There are also several other types of concatenation (which can all be written in terms of +). For example, / concatenates documents with a newline in between:

// equivalent to Doc.text("first line") + Doc.line + Doc.text("second line")
val doc5 = Doc.text("first line")  / Doc.text("second line")
// doc5: Doc = Concat(
//   a = Text(str = "first line"),
//   b = Concat(
//     a = FlatAlt(default = Doc(...), whenFlat = Text(str = " ")),
//     b = Text(str = "second line")
//   )
// )
val doc6 = Doc.text("first line")  :/ "second line"
// doc6: Doc = Concat(
//   a = Text(str = "first line"),
//   b = Concat(
//     a = FlatAlt(default = Doc(...), whenFlat = Text(str = " ")),
//     b = Text(str = "second line")
//   )
// )
val doc7 = "first line" /: Doc.text("second line")
// doc7: Doc = Concat(
//   a = Text(str = "first line"),
//   b = Concat(
//     a = FlatAlt(default = Doc(...), whenFlat = Text(str = " ")),
//     b = Text(str = "second line")
//   )
// )