8. Writing results out¶
A program's value is computed first, and written second. How it is written is a presentation format — plain text, HQL, Markdown, JSON, YAML, CSV, or a diagram — and the format is a type, so a program that formats a value knows what kind of text it holds.
The programs of this chapter read the shelf of chapter 6, with its typed views, and the book shows these lines once:
shelf = [
{title: "Frankenstein", author: "Mary Shelley", year: 1818, genre: "novel", pages: 280, status: "done"},
{title: "Dune", author: "Frank Herbert", year: 1965, genre: "novel", pages: 412, status: "reading"},
{title: "The Hobbit", author: "J. R. R. Tolkien", year: 1937, genre: "novel", pages: 310, status: "todo"},
{title: "Leaves of Grass", author: "Walt Whitman", year: 1855, genre: "poetry", pages: 145, status: "todo"},
{title: "A Room of One's Own", author: "Virginia Woolf", year: 1929, genre: "essay", pages: 112, status: "done"},
{title: "The Common Reader", author: "Virginia Woolf", year: 1925, genre: "essay", pages: 304, status: "todo"},
{title: "Mrs Dalloway", author: "Virginia Woolf", year: 1925, genre: "novel", pages: 194, status: "reading"},
]
fn title(b: Data) : String = match b.title { some(String t) => t, _ => "" }
fn pages(b: Data) : Int = match b.pages { some(Int n) => n, _ => 0 }
Formats are a category of their own¶
Each format is a Format: Text, Lines, Hql, Markdown, Hmd, Json,
JsonLines, Yaml, Csv, D2 and Mermaid. A format describes how values
are written, not the text itself: a step named for a format encodes any value
in it, and what comes out is a String:
shelf | filter(b => b.status == "todo") | map(title) | markdown
- The Hobbit
- Leaves of Grass
- The Common Reader
shelf | filter(b => b.status == "todo") | map(title) | markdown
String
A formatted value is a string that remembers its format, so a host prints it as it is. A later step may read it, and comparing it with a string compares the text:
([1, 2] | json) == "[\n 1,\n 2\n]"
true
The longhand, format¶
markdown is the shorthand for format(Markdown). The longhand takes two
options. limit keeps the first n elements and says how many were cut:
shelf
| map(title)
| format(Text, limit = 3)
["Frankenstein", "Dune", "The Hobbit"]
… 4 more not shown
A list of records in Markdown is a bullet list. A table is a value of its own
type, Table: Table(xs, columns = [...]) takes one row per record and the
columns in the order written, and every format writes it as a table, a pipe
table in Markdown:
shelf
| map(b => {title: title(b), pages: pages(b)})
| Table(columns = ["title", "pages"])
| markdown
| title | pages |
| ------------------- | ----- |
| Frankenstein | 280 |
| Dune | 412 |
| The Hobbit | 310 |
| Leaves of Grass | 145 |
| A Room of One's Own | 112 |
| The Common Reader | 304 |
| Mrs Dalloway | 194 |
The same table in Csv is a header row and one row per record, in Json an
array of objects with their keys in column order, and in Text space-aligned
columns. renderer names one of a format's encoders for a shape over the
project's choice, format(Markdown, renderer = "mermaid") for a graph; a table
renderer, such as pipe, writes only a Table.
A format that cannot write a value is a type error, reported before the program runs:
1 | d2
check error[type-mismatch]: no renderer writes Int as D2
--> <expression>:1:5
|
1 | 1 | d2
| ^^
Choosing the format from outside¶
A program that does not format its result is written as HQL — the literal you
would type to get the value back, a string in quotes. --to text writes a
string bare instead, and --to names any other format without changing the
program:
shelf | filter(b => b.status == "todo") | map(title)
- The Hobbit
- Leaves of Grass
- The Common Reader
--to none evaluates the program and writes nothing. --envelope json wraps
the answer for another program to read: the type, the value, and every
diagnostic the run wrote:
shelf | filter(b => b.status == "todo") | size
{
"diagnostics": [],
"origin": "<expression>",
"type": "Int",
"value": 3
}
The order is fixed: a format step in the program wins, then --to, then the
host's own — Markdown for a document, HQL for a terminal.
A project chooses its renderers¶
Which renderer writes a type in a format is a project's choice, in the
hql.toml of the vault a program runs in (chapter 16), over
the built-in defaults. A graph in Markdown is a D2 diagram unless the file
says otherwise:
[format.Graph]
markdown = "mermaid"
A renderer or format the binary does not have fails loading the vault, so the mistake is reported before any query runs.
What is next¶
Every value so far was written to the terminal. Chapter 9 reads files, standard input and the network, and writes files and standard output, without leaving the language.