Inspect.Algebra

A set of functions for creating and manipulating algebra documents, as described in "Strictly Pretty" (2000) by Christian Lindig.

An algebra document is represented by an Inspect.Algebra node or a regular string.

iex> Inspect.Algebra.empty
:doc_nil
iex> "foo"
"foo"

With the functions in this module, we can concatenate different elements together and render them:

iex> doc = Inspect.Algebra.concat(Inspect.Algebra.empty, "foo")
iex> Inspect.Algebra.pretty(doc, 80)
"foo"

The functions nest/2, space/2 and line/2 help you put the document together into a rigid structure. However, the document algebra gets interesting when using functions like break/2, which converts the given string into a line break depending on how much space there is to print. Let's glue two docs together with a break and then render it:

iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"

Notice the break was represented as is, because we haven't reached a line limit. Once we do, it is replaced by a newline:

iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"

Finally, this module also contains Elixir related functions, a bit tied to Elixir formatting, namely surround/3 and surround_many/5.

Implementation details

The original Haskell implementation of the algorithm by Wadler relies on lazy evaluation to unfold document groups on two alternatives: :flat (breaks as spaces) and :break (breaks as newlines). Implementing the same logic in a strict language such as Elixir leads to an exponential growth of possible documents, unless document groups are encoded explictly as :flat or :break. Those groups are then reduced to a simple document, where the layout is already decided, per Lindig.

This implementation slightly changes the semantic of Lindig's algorithm to allow elements that belong to the same group to be printed together in the same line, even if they do not fit the line fully. This was achieved by changing :break to mean a possible break and :flat to force a flat structure. Then deciding if a break works as a newline is just a matter of checking if we have enough space until the next break that is not inside a group (which is still flat).

Custom pretty printers can be implemented using the documents returned by this module and by providing their own rendering functions.

Source

Summary

break()
break(s)

Document entity representing a break. This break can be rendered as a linebreak or as spaces, depending on the mode of the chosen layout or the provided separator

concat(docs)

Concatenates a list of documents

concat(x, y)

Concatenates two document entities. Takes two arguments: left doc and right doc. Returns a DocCons doc

empty()

Returns :doc_nil which is a document entity used to represent nothingness. Takes no arguments

folddoc(list1, f)

Folds a list of document entities into a document entity using a function that is passed as the first argument

glue(x, y)

Inserts a break between two docs. See break/1 for more info

glue(x, g, y)

Inserts a break, passed as the second argument, between two docs, the first and the third arguments

group(d)

Returns a group containing the specified document

line(x, y)

Inserts a mandatory linebreak between two document entities

nest(x, i)

Nests document entity x positions deep. Nesting will be appended to the line breaks

pretty(d, w)

The pretty printing function

space(x, y)

Inserts a mandatory single space between two document entities

surround(left, doc, right)

Surrounds a document with characters

surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)

Maps and glues a collection of items together using the given separator and surrounds them. A limit can be passed which, once reached, stops gluing and outputs "..." instead

to_doc(arg, opts)

Converts an Elixir structure to an algebra document according to the inspect protocol

Types

t :: :doc_nil | :doc_line | doc_cons_t | doc_nest_t | doc_break_t | doc_group_t | binary

Functions

break()

Specs:

  • break :: doc_break_t
Source
break(s)

Specs:

  • break(binary) :: doc_break_t

Document entity representing a break. This break can be rendered as a linebreak or as spaces, depending on the mode of the chosen layout or the provided separator.

Examples

Let's glue two docs together with a break and then render it:

iex> doc = Inspect.Algebra.glue("a", " ", "b")
iex> Inspect.Algebra.pretty(doc, 80)
"a b"

Notice the break was represented as is, because we haven't reached a line limit. Once we do, it is replaced by a newline:

iex> doc = Inspect.Algebra.glue(String.duplicate("a", 20), " ", "b")
iex> Inspect.Algebra.pretty(doc, 10)
"aaaaaaaaaaaaaaaaaaaa\nb"
Source
concat(docs)

Specs:

  • concat([t]) :: doc_cons_t

Concatenates a list of documents.

Source
concat(x, y)

Specs:

  • concat(t, t) :: doc_cons_t

Concatenates two document entities. Takes two arguments: left doc and right doc. Returns a DocCons doc

Examples

iex> doc = Inspect.Algebra.concat "Tasteless", "Artosis"
iex> Inspect.Algebra.pretty(doc, 80)
"TastelessArtosis"
Source
empty()

Specs:

  • empty :: :doc_nil

Returns :doc_nil which is a document entity used to represent nothingness. Takes no arguments.

Examples

iex> Inspect.Algebra.empty
:doc_nil
Source
folddoc(list1, f)

Specs:

  • folddoc([t], (t, t -> t)) :: t

Folds a list of document entities into a document entity using a function that is passed as the first argument.

Examples

iex> doc = ["A", "B"]
iex> doc = Inspect.Algebra.folddoc(doc, fn(x,y) ->
...>   Inspect.Algebra.concat [x, "!", y]
...> end)
iex> Inspect.Algebra.pretty(doc, 80)
"A!B"
Source
glue(x, y)

Specs:

  • glue(t, t) :: doc_cons_t

Inserts a break between two docs. See break/1 for more info.

Source
glue(x, g, y)

Specs:

  • glue(t, binary, t) :: doc_cons_t

Inserts a break, passed as the second argument, between two docs, the first and the third arguments.

Source
group(d)

Specs:

  • group(t) :: doc_group_t

Returns a group containing the specified document.

Examples

iex> doc = Inspect.Algebra.group(
...>   Inspect.Algebra.concat(
...>     Inspect.Algebra.group(
...>       Inspect.Algebra.concat(
...>         "Hello,",
...>         Inspect.Algebra.concat(
...>           Inspect.Algebra.break,
...>           "A"
...>         )
...>       )
...>     ),
...>     Inspect.Algebra.concat(
...>       Inspect.Algebra.break,
...>       "B"
...>     )
...> ))
iex> Inspect.Algebra.pretty(doc, 80)
"Hello, A B"
iex> Inspect.Algebra.pretty(doc, 6)
"Hello,\nA B"
Source
line(x, y)

Specs:

  • line(t, t) :: doc_cons_t

Inserts a mandatory linebreak between two document entities.

Examples

iex> doc = Inspect.Algebra.line "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes\nWadler"
Source
nest(x, i)

Specs:

  • nest(t, non_neg_integer) :: doc_nest_t

Nests document entity x positions deep. Nesting will be appended to the line breaks.

Examples

iex> doc = Inspect.Algebra.nest(Inspect.Algebra.concat(Inspect.Algebra.break, "6"), 5)
iex> Inspect.Algebra.pretty(doc, 80)
" 6"
Source
pretty(d, w)

Specs:

  • pretty(t, non_neg_integer | :infinity) :: binary

The pretty printing function.

Takes the maximum width and a document to print as its arguments and returns the string representation of the best layout for the document to fit in the given width.

Source
space(x, y)

Specs:

  • space(t, t) :: doc_cons_t

Inserts a mandatory single space between two document entities.

Examples

iex> doc = Inspect.Algebra.space "Hughes", "Wadler"
iex> Inspect.Algebra.pretty(doc, 80)
"Hughes Wadler"
Source
surround(left, doc, right)

Specs:

  • surround(binary, t, binary) :: t

Surrounds a document with characters.

Puts the document between left and right enclosing and nesting it. The document is marked as a group, to show the maximum as possible concisely together.

Examples

iex> doc = Inspect.Algebra.surround "[", Inspect.Algebra.glue("a", "b"), "]"
iex> Inspect.Algebra.pretty(doc, 3)
"[a\n b]"
Source
surround_many(left, docs, right, limit, fun, separator \\ @surround_separator)

Specs:

  • surround_many(binary, [any], binary, integer | :infinity, (term -> t), binary) :: t

Maps and glues a collection of items together using the given separator and surrounds them. A limit can be passed which, once reached, stops gluing and outputs "..." instead.

Examples

iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", :infinity, &integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 5)
"[1,\n 2,\n 3,\n 4,\n 5]"

iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1))
iex> Inspect.Algebra.pretty(doc, 20)
"[1, 2, 3, ...]"

iex> doc = Inspect.Algebra.surround_many("[", Enum.to_list(1..5), "]", 3, &integer_to_binary(&1), "!")
iex> Inspect.Algebra.pretty(doc, 20)
"[1! 2! 3! ...]"
Source
to_doc(arg, opts)

Specs:

Converts an Elixir structure to an algebra document according to the inspect protocol.

Source