Libraries, Not Keywords
This is part of a short series answering David Barbour’s questions about Koru. Several of them turn out to share a shape — should this be a language feature? should “session” be a keyword instead of “event”? do you have a built-in notion of singletons? — and our answer keeps coming out the same way, so it’s worth saying once, plainly, and then pointing the other posts here.
The answer is: usually it shouldn’t be a language feature. It should be a library — and the work is making the library substrate strong enough to carry it.
That’s not a dodge or a shortfall. It’s a bet about where power should live in a language. David’s instinct, coming from formal calculi, is to grow the grammar: if effects compose into protocols, name that, type it, put session in the language. Koru’s instinct is the opposite — keep the grammar small, and put the power in a few composable compiler primitives that libraries build on. So when he asks “should this be a feature,” our honest answer is often “we wouldn’t make it one at all.” Here’s why we can say that with a straight face.
Where the language ends is not where you think
Start with the most radical version of the claim, because it sets the scale: a keyword in Koru is just an event you don’t have to fully qualify. That is
the whole of it. if is a keyword — but “keyword” here carries no meaning
beyond “you may write this event without its module path.”
~if and ~for are ordinary events, declared in koru_std/control.kz and
available only because you import it. The one thing that lets you write a bare ~if instead of a qualified ~std/control:if is a one-word annotation, [keyword]. That is the entire difference between a keyword and any other event —
a module event without it, like regex’s match, simply has to be qualified.
So control flow isn’t grammar. It’s a library you happen to always import.
Now the part that surprised even us to write down: the obligation checker isn’t
grammar either. Everything in the previous post —
issue in, discharge out, KORU030 was not discharged — is not built into the
language. It’s a compiler pass. Koru’s checker passes all come from compiler.kz and run in a pipeline orchestrated by a single event, compiler:coordinate, which —
like any abstract event — you can override. The default pipeline runs the phantom/obligation
checker; a different one need not. The discipline is on by default, it has a
warn-only mode, and it can be turned off entirely:
Sit with that. The thing that feels most like the soul of Koru — the obligation system — is a default-on, removable pass, not a language primitive. Compile with a coordinate pipeline that drops it and you have a different, looser language built from the very same grammar.
So when David asks whether some construct should be “in the language,” the honest Koru answer has an unusual edge: there is very little in the language in the sense he means. The grammar is events, flows, and branches. Control flow is a library. The type discipline is a pass. The line he’s asking us to draw — feature versus library — is one we mostly don’t have.
The exemplar: a bounded protocol that’s already a library
Koru has a high-performance computation kernel — a construct where you declare a data shape, then operate on it relationally, and the compiler decides layout and iteration:
~std/kernel:shape(Body) { x: f64, y: f64, mass: f64 }
~std/kernel:init(Body) { x: 42.0, y: 0, mass: 1.5 } The interesting part isn’t the math. It’s that inside a kernel, only certain
things are legal. No for loops, no branch constructors, no arbitrary events —
a kernel body is a bounded sub-tree with a defined set of permitted operations.
That is precisely the shape of a protocol: “within this scope, these
interactions and no others.” It is, in miniature, the session idea David is
reaching for — a sub-tree whose legal contents are constrained and checked.
And every bit of it is a library. There is no kernel keyword. std/kernel is an ordinary Koru module — 1,500 lines of [transform] and [norun] events —
that hangs off one real language primitive: an annotation called claims_descendants, which means “this transform owns the sub-tree beneath
it.” The kernel’s init is declared [comptime|transform|claims_descendants],
claims the operations that follow it, walks them, and rejects what doesn’t belong.
The enforcement is real, and — honestly — partial. The structural rules are in:
The legal-event whitelist — “only kernel operations, reject arbitrary user and stdlib events” — is still aspirational, and we keep it pinned red so it stays visible instead of forgotten:
So the kernel is a working proof that Koru can express a bounded protocol as a library — and an honest record of how far the enforcement has actually gotten.
The substrate: annotations meaty enough to carry it
A library that does compiler-level work needs to attach real directives to its
constructs — not just a flag, but parameters, configuration, intent. For a long
time annotations were inline and pipe-delimited: [comptime|transform]. That
runs out of room fast once the directives take arguments.
So the substrate grew. Annotations can now be written vertically, one directive per line, with parameters:
~[
- comptime
- runtime
- optimize(level: 3)
- gpu(target: "metal", precision: "half")
] event vertical-params {} This parses to the same AST as the inline form — it’s purely about giving library authors room to write meatier directives legibly.
The road keeps going past what’s paved. The aspirational end of it is literate annotations — annotation blocks as micro-documents, markdown prose interleaved
with - directives, the parser keeping the directives and ignoring the prose, so
metadata can explain and instruct in one place. That one’s not landed:
Widening the annotation language isn’t a cosmetic change. It’s the thing that decides whether the next bounded-protocol construct — sessions, say — can be built the way the kernel was, as a library, or whether it would have to fall back into the grammar.
The bet, stated plainly
Put the three together and the stance is: a small grammar, a few sharp compiler
primitives (claims_descendants and the transform machinery), and an annotation
language rich enough that libraries can carry real directives. Power lives in the
composition, not in keyword count.
This is a real bet, and worth naming its risks. A library-grown construct can be under-enforced — the kernel’s red whitelist is exactly that. A formal language feature gives you guarantees a library has to earn test by test. David’s grammar-first instinct buys soundness up front; our library-first instinct buys a grammar that doesn’t ossify, at the cost of having to build — and pin — the enforcement ourselves. We think that’s the right trade for a language still being discovered, but it is a trade.
What this answers
So when his thread asks whether effects-having-effects deserves to be a session in the language, or whether Koru needs a built-in notion of singletons, the
answer this post lets the others give is the same one twice: we wouldn’t add a
keyword for it. We’d build it the way we built the kernel — a bounded construct
in a library, standing on claims_descendants and the annotation substrate — and
the work we’d point to as evidence we mean it is the annotation language quietly
getting meatier underneath.
The language we didn’t grow is the point.