6.9. Output formats
A command’s return value is serialized and written to standard output. The default form is JSON:
$ ./sift summarize hits.json
[["notes\/todo.txt",2],["notes\/2026\/plan.txt",2]]
/ is escaped as \/, which JSON permits and which some encoders do. It is
the same string either way.
The nexus option -f picks a different form. Which forms are available does not
depend on the program — serialization is the runtime’s job, not the tool’s, so
every Morloc command can emit every form its type supports:
| Form | Notes |
|---|---|
|
The default. Human-readable, lossy on integer width. |
|
One element per line. Meaningful for list-shaped results; a scalar is one line. |
|
MessagePack. Compact, exact. |
|
Morloc’s in-memory binary form, written out. Carries the value’s schema. |
|
A Morloc wire packet: the value plus its schema and framing. This is what
|
|
Apache Arrow IPC and Parquet. Requires a |
|
Requires a |
Because the reader detects the format from the bytes, a value written in one form is read back without being told which:
$ ./sift -f mpk scan the notes > hits.mpk
$ ./sift summarize hits.mpk
[["notes\/todo.txt",2],["notes\/2026\/plan.txt",2]]
-f jsonl is the form to reach for when the next thing in the pipeline is a
line-oriented Unix tool:
$ ./sift -f jsonl scan the notes
{"path":"notes\/todo.txt","line":2,"text":"fix the parser"}
{"path":"notes\/todo.txt","line":3,"text":"write the manual"}
{"path":"notes\/2026\/plan.txt","line":1,"text":"fix the build"}
{"path":"notes\/2026\/plan.txt","line":2,"text":"ship the manual"}
Asking for a form the type cannot produce is an error, not a silent approximation:
$ ./sift -f csv summarize hits.json
Error: --format=arrow|parquet|csv requires a Table return type
Two more nexus options shape the output. -o writes to a file instead of
stdout. -p pretty-prints: JSON gets indentation, and a top-level Str is
printed as text rather than as a quoted JSON string.
$ ./sift -p summarize hits.json
[
[
"notes\/todo.txt",
2
],
[
"notes\/2026\/plan.txt",
2
]
]
-z compresses -f packet output; it is covered with the rest of the
compression settings in Compression.
6.9.1. Nothing to report
A command that returns () or a top-level Null prints nothing at all. That
matches the Unix convention that a tool with no result says nothing, and it is
what you want when a Morloc command feeds grep, xargs, or a status check — a () carries no information, and a top-level None usually means "it ran and
there was nothing to say".
A small program with an optional result, to show it with:
def lookup(key, table):
return dict(table).get(key)
def pair():
return [5, None]
module nulls (lookupKey, pair)
import root-py
import map-py
source Py from "nulls.py" ("lookup" as lookupKey, "pair")
--' Look up a key, or nothing
lookupKey :: Str -> Map Str Str -> ?Str
--' A pair whose second element is Unit
pair :: (Int, ())
$ ./nulls lookupKey zz '[["a","1"],["b","2"]]'
$ echo $?
0
When the distinction matters — a downstream consumer that needs null to mean
"a null result" as against an empty file meaning "the process died" — pass
--keep-null:
$ ./nulls --keep-null lookupKey zz '[["a","1"],["b","2"]]'
null
Suppression is a JSON-only convenience. The binary forms always write a well-formed nil, so a reader sees the bytes it expects:
$ ./nulls -f mpk lookupKey zz '[["a","1"]]' | od -An -tx1
c0
A null inside a value is never suppressed — the shape carries information
the consumer needs:
$ ./nulls pair
[5,null]
6.9.2. Failure
Errors go to standard error and the process exits non-zero, so a Morloc command
behaves in a set -e script or a && chain the way any other tool does:
$ ./sift summarize nosuch.json
Error: failed to parse argument #0: file 'nosuch.json' not found
$ echo $?
1
Errors raised inside a pool name the function and the source position that raised them:
$ ./sift total < /dev/urandom
Error: run failed
...
@next: stdin is not a morloc packet; expected a morloc data or stream packet. Foreign formats (JSON, MessagePack, CSV, ...) are not supported on stdin. A morloc program writes packets only when asked: add `-f packet` to the command on the writing end of this pipe.
at total [py] (mid=4, sift.loc:2:40)