Writing with target and sink¶
Writing has two forms, chosen by the shape of the value. target(endpoint,
Format) writes one completed value as one document. sink(endpoint, Format)
writes one document per value, as each arrives. Both give Unit, and a
Unit result prints nothing, so what a program below prints is what the
step wrote.
One document with target¶
source(file("examples/io/sightings.jsonl"))
| map(s => {species: s.species, site: s.site})
| target(stdout, Csv)
site,species
meadow,barn owl
hedge,wren
barn,barn owl
river,kingfisher
A Seq written by target is one document: in Csv a header and a row per
element, in Json one array:
source(file("examples/io/sightings.jsonl")) | map(s => s.species) | target(stdout, Json)
[
"barn owl",
"wren",
"barn owl",
"kingfisher"
]
Over a file, target replaces the file atomically: the document is written
beside the destination and renamed over it when complete, so a reader never
sees a partial file, and a failed write leaves the old file in place. Run
from a directory of your own:
source(file("examples/io/sightings.jsonl"))
| map(s => {species: s.species, site: s.site})
| target(file("sites.csv"))
$ cat sites.csv
site,species
meadow,barn owl
hedge,wren
barn,barn owl
river,kingfisher
One document per value with sink¶
source(file("examples/io/sightings.jsonl")) | map(s => s.species) | sink(stdout, Json)
"barn owl"
"wren"
"barn owl"
"kingfisher"
A Seq written by sink in Json is one JSON document per line, which is
what JsonLines and Json both read back. A stream may never complete, so
sink is how a stream is written. target over a stream that is not a Seq
is a type error, and the diagnostic gives the two ways out:
source(stdin, Json) | target(file("all.json"))
check error[type-mismatch]: `target` writes one completed value, and Stream<Data> may not end; write it piece by piece with `sink`, or bound it with `take(n)` first
--> <expression>:1:23
|
1 | source(stdin, Json) | target(file("all.json"))
| ^^^^^^^^^^^^^^^^^^^^^^^^
What each step accepts, by the shape of the values:
Seq<T> |
Stream<T> that is not a Seq |
|
|---|---|---|
target |
one document | a type error: sink, or take(n) first |
sink |
one document per element | one document per value |
A batch is a list, so collect(2) | sink(stdout, Json) writes one JSON array
per two values (finite or live).
Replace or append¶
Writing file(p) replaces the file. Writing file(p).end appends to it, and
creates it when it is missing. Run twice, once with each species:
[{species: "wren"}] | sink(file("log.jsonl").end)
$ cat log.jsonl
{"species":"wren"}
{"species":"kite"}
sink over file(p) truncates the file before its first document and writes
each document as it arrives, since a stream that never ends cannot be renamed
into place.
One evaluation replaces a file at most once, appends to its end any number of times, and never both. Reading a file the same evaluation has replaced is an evaluation failure too, as is a missing parent directory: neither step creates a directory.
1 | target(file("a.json"))
2 | target(file("a.json"))
evaluation failure: a.json: this evaluation has already replaced the file; append to `file(p).end`, or write it once
--> <expression>:2:5
|
2 | 2 | target(file("a.json"))
| ^^^^^^^^^^^^^^^^^^^^^^
Branches that write¶
A tee branch from the SQL-like queries chapter
may end in target or sink. The branch receives each value before the
trunk does and writes it, and the trunk goes on with every value:
source(stdin, Json)
| tee dusk =
filter(s => s.seen == "dusk")
| sink(file("dusk.jsonl").end, Json)
| map(s => s.species)
| sink(stdout, Text)
$ cat examples/io/sightings.jsonl | hql '…'
barn owl
wren
barn owl
kingfisher
$ cat dusk.jsonl
{"seen":"dusk","site":"meadow","species":"barn owl"}
One program reads sightings as they arrive, appends those seen at dusk to a file, and prints every species. A failed write on any branch ends the evaluation with that error.
Standard output and the result¶
target(stdout, F) and sink(stdout, F) write while the program runs. The
result of a program is a different thing: the host presents it after
evaluation, in the format --to names
(hql on the command line). A program that ends in
target or sink has the result Unit, and prints nothing beyond what it
wrote.