match, rearrange, the Z placeholder, and the other clever ones.
Z is the "it" in a pipeline. Think of it as shorthand for "whatever comes in from the pipe":
5 | apply(Z + 1) | collect # 6
5 | apply(Z * Z) | collect # 25
5 | apply(Z > 3) | collect # True
[1, 2] | apply(Z[0] + Z[1]) | collect # 3
Under the hood, Z + 1 isn't just "do a sum." It's a symbolic
expression โ a tiny AST you can pass around as a value.
If you wrote lambda x: x + 1, Python builds a closure object with
bytecode. If you write Z + 1, Zef builds a symbolic value that
says "add one to whatever comes in." That value can be introspected, sent over
the network, stored in a database. Lambdas can't.
F is Z's cousin, specialized for "pick this field":
data = {'name': 'Alice', 'age': 30}
data | F.name | collect # 'Alice'
# chain for deep paths
doc | F.user | F.profile | F.bio | collect
And it composes with Z-style operators:
people | filter(F.age > 18) | collect
people | sort_by(F.age) | collect
people | group_by(F.dept) | collect
Python's if/elif/else with types as first-class patterns:
142 | match([
({42, -42}, 'special'), # set = "matches any member"
(Int, to_string), # type = "matches instances"
(String, identity), # ditto
(Any, 'fallback'),
]) | collect
# โ '142'
Patterns are checked top-to-bottom. First match wins.
| pattern | matches when |
|---|---|
42 | value equals 42 |
{1, 2, 3} | value is in the set |
Int | value is_a Int |
Int & (Z > 0) | refinement type |
Any | catch-all |
items = [1, 2.5, 'hello', [1, 2], True]
items | map(match([
(Int, Z | apply(lambda x: f"int: {x}")),
(Float, Z | apply(lambda x: f"float: {x}")),
(String, Z | apply(lambda x: f"str: {x}")),
(Array, constant_func('list')),
(Bool, constant_func('bool')),
(Any, constant_func('other')),
])) | collect
Put narrower patterns first. Bool is a subtype of Int
in Python โ if you match Int first, True becomes
"int: True". Swap the order or use an explicit
isinstance(..., bool)-style guard.
zReshape data using a template that looks like the target output. Use
lowercase z (a template placeholder) to extract from the input:
['Alice', 30, 'Berlin'] | rearrange({
'name': z[0],
'age': z[1] + 10,
'city': z[2] | to_upper_case,
}) | collect
# {'name': 'Alice', 'age': 40, 'city': 'BERLIN'}
Think of rearrange as "the template is the shape of the answer."
Zef walks the template, replaces every z[i] with the i-th input,
evaluates expressions, and returns the reshaped value.
# dict template โ dict
[1, 2] | rearrange({'a': z[0], 'b': z[1]}) | collect
# list template โ list
[1, 2, 3] | rearrange([z[2], z[0], z[1]]) | collect
# entity template โ entity
['Alice', 30] | rearrange(
ET.Person(name=z[0], age=z[1] + 10)
) | collect
['Alice', 30, 'Berlin', 'Engineer'] | rearrange({
'personal': {'name': z[0], 'age': z[1]},
'professional': {'city': z[2], 'role': z[3]},
}) | collect
[10, 11, 'hello'] | apply_functions([
add(1),
multiply(2),
to_upper_case,
]) | collect
# [11, 22, 'HELLO']
The nth function applies to the nth element. Lengths must match.
'b' | apply_dict({'a':1, 'b':2, 'c':3}) | collect # 2
# with a default:
'z' | apply_dict({'a':1}, 'not found') | collect # 'not found'
# combine with map for batch lookups:
['US', 'DE', 'JP'] | map(apply_dict({
'US': 'United States',
'DE': 'Germany',
'JP': 'Japan',
})) | collect
[1, 2, 3, 4, 5] | sliding(3) | collect
# [[1,2,3], [2,3,4], [3,4,5]]
# moving sum
[1, 2, 3, 4, 5] | window_reduce(add, 3) | collect
# [6, 9, 12]
# moving average helper
avg3 = window_reduce(add, 3) | map(Z / 3)
[10, 20, 30, 40] | avg3 | collect
# [20.0, 30.0]
data | op1 | log | op2 | log | op3 | collect
# prints:
# ๐ชต: <value after op1>
# ๐ชต: <value after op2>
log is a pass-through โ it prints the value and hands it to the next stage unchanged.
data | op1 | log('step 1') | op2 | log('step 2') | collect
Suppose you have a list of log lines. You want to:
service namelines = [
'{"level":"INFO","service":"api","msg":"ok","ts":1}',
'{"level":"ERROR","service":"api","msg":"boom","ts":2}',
'{"level":"ERROR","service":"db","msg":"slow","ts":3}',
'{"level":"ERROR","service":"api","msg":"hmm","ts":4}',
'{"level":"INFO","service":"db","msg":"ok","ts":5}',
]
summary = (
lines
| map(parse_json) # [{...}, ...]
| filter(F.level == 'ERROR') # [{...}, ...]
| group_by(F.service) # {'api':[...], 'db':[...]}
| items # [('api',[...]), ('db',[...])]
| map(rearrange({
'service': z[0],
'count': z[1] | length,
'last': z[1] | sort_by(F.ts) | last | F.msg,
}))
| collect
)
# [{'service':'api', 'count':2, 'last':'hmm'},
# {'service':'db', 'count':1, 'last':'slow'}]
Every piece of the pipeline reads left-to-right like a sentence. No temp
variables, no loops. If you want to add a step ("also sort by count desc"),
you drop in | sort_by(F.count, descending=True). Evolution is
easy because every stage has a clear input shape and output shape.
| op | use case | example |
|---|---|---|
match(cases) | dispatch by type/value | see above |
match_on(sel, cases) | dispatch on extracted key | match_on(F.status, [...]) |
rearrange(tmpl) | reshape by template | {'n': z[0]} |
apply_functions([f,g]) | per-element fns | zip lengths |
apply_dict(d) | lookup table | map keys โ values |
sliding(n) | windows of n | moving analyses |
window_reduce(f, n) | reduce per window | moving sum |
log | debug-print | drop in anywhere |
Next up: Zen notation โ the idea that Python literals ARE the data language. โ