๐Ÿ 

the three big ideas

if you remember nothing else from this book, remember this page.

Zef's architecture fits on a napkin. All of its code falls into exactly three buckets:

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— โ•‘ E V E R Y T H I N G โ•‘ โ•‘ I S A V A L U E โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• | โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” | | | โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Zef โ”‚ โ”‚ Zef โ”‚ โ”‚ Zef โ”‚ โ”‚ TYPES โ”‚ โ”‚ OPS โ”‚ โ”‚ FX โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ "the nouns" "the verbs" "the side effects"

#1 ยท Zef Types โ€” the nouns

These are the shapes data can take. Some you already know (Int, String, Dict, Array). Some are new (ET.Person, Graph, Signal, DictDatabase).

Int, Float, String, Bool, Bytes          # primitives
Array, Dict, Set, Tuple                   # containers
Time, PngImage, Mp3Sound                  # rich values
ET.Person, ET.Invoice, ET.City                # your domain types
Graph, DictDatabase, Signal, Topic       # structural types

the type universe is open

You invent new types by just writing ET.Whatever. No class definition, no registration form. The first time Zef sees ET.Dragon, it assigns it a compact binary encoding and moves on.

#2 ยท ZefOps โ€” the verbs

ZefOps are pure functions. Pure = given the same input, always the same output, no side effects. There are about 500 shipped with Zef, all composable with the pipe operator.

map, filter, reduce, apply, match, rearrange,
sort, take, drop, group_by, join, flatten,
first, last, length, reverse, unique,
parse_json, to_json, split, trim, to_upper_case, ...
mental model #2

A ZefOp is a tiny machine with a data input. The pipe plugs the output of one into the input of the next:

data โ”€โ”€โ–ถ[ map(+1) ]โ”€โ”€โ–ถ[ filter(>3) ]โ”€โ”€โ–ถ[ reduce(+) ]โ”€โ”€โ–ถ result (5 values) (2 values) (1 number)

And the whole chain โ€” including the data! โ€” is just one big value until you | collect.

#3 ยท FX โ€” the side effects

Effects are values describing something the runtime will do. Printing to a screen, calling an HTTP API, starting a server, writing a file โ€” all "verbs of the outside world."

FX.Print(content='hi')
FX.HTTPRequest(url='https://example.com')
FX.StartHTTPServer(routes={'/': 'hello'}, port=8000)
FX.Publish(target=some_topic, content='msg')
FX.CreateSignal(value=0)
FX.StartActor(input=topic, handler=h, initial_state=0)

Each one is data. It literally is. You can print it:

eff = FX.HTTPRequest(url='https://example.com')
print(eff)          # ET.HTTPRequest(url='https://example.com')
type(eff)           # Entity_
eff.url             # 'https://example.com'

# nothing has happened yet! NOW it happens:
eff | run

"wait this is just a monad"

Sort of, yes. It's the same trick Haskell's IO and Elm's Cmd use โ€” defer the side effect by turning it into data. But Zef skips the category theory vocabulary entirely: these are just ordinary values you happen to hand to a special word (run) when you actually want them to happen.

a tiny program using all three

Here's a program that uses a type (Dict), a bunch of ZefOps (map, filter, apply), and an FX (Print):

from zef import *

people = [
    {'name': 'Ada',   'age': 36},
    {'name': 'Bea',   'age': 17},
    {'name': 'Carl',  'age': 28},
]

# Pure pipeline โ€” returns a list
adults = (
    people
    | filter(F.age >= 18)       # <- ZefOp
    | map(F.name | to_upper_case)   # <- ZefOp
    | collect
)
# adults == ['ADA', 'CARL']

# Effect โ€” prints a line per adult
for name in adults:
    FX.Print(content=f'๐Ÿ‘ค {name}') | run
the unifying trick

Look at the three sentences below. They mean different things in most languages. In Zef, they all produce the same kind of thing: a value.

Int โ†’ a value (type) map(add(1)) โ†’ a value (ZefOp) FX.Print(content='hi') โ†’ a value (effect)

That's why you can throw any of them in a list, compare them, send them over a wire, serialize them to disk. Uniformity is the feature.

one more thing: Zef values are contiguous

A small but important detail. When Zef says "value," it really means: a chunk of memory you can memcpy.

normal python dict: zef dict: โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ ptr โ”‚โ”€โ”€โ”‚ key โ”‚ โ”‚ type | len | k1 v1 k2 v2โ”‚ โ”‚ ptr โ”‚โ”€โ”€โ”‚ val โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ ptr โ”‚โ”€โ”€โ”‚ key โ”‚ one contiguous chunk โ”‚ ... โ”‚ โ”‚ val โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”˜ โ†ณ serializable for free (pointer jungle) โ†ณ memcpy over the wire works

why this matters for you

Because of this design, a Zef value that sits in memory is the same bytes as one sitting in a file or being sent over a socket. There's no serialization step. When you pull a String from a database, you get back the exact same String type you put in.

We'll see this pay off again and again โ€” databases don't need ORMs, IPC doesn't need JSON, graphs don't need schemas.

the rhythm you'll repeat a thousand times

VALUE | OP | OP | collect  or  run

That's it. That's a Zef program.

try it right now

If you have zef installed:

from zef import *

[1, 2, 3] | map(Z * Z) | collect   # โ†’ [1, 4, 9]
FX.Print(content='๐ŸŒฟ') | run            # โ†’ prints ๐ŸŒฟ

Feel the shape.

Next up: the pipe itself โ€” why it matters and how it bends Python's bitwise OR. โ†’