← Home
petri-net dsl domain-driven-design little-languages composition state-machine formal-methods forth tla-plus

The Little Language Thesis

In 1986, Jon Bentley wrote a column in Communications of the ACM called "Little Languages." His argument: for many problems, the right move isn't a library or a framework — it's a tiny, purpose-built language. make, awk, pic, regular expressions. Languages so small you can learn them in an afternoon and use them for years.

Seventeen years later, Eric Evans published Domain-Driven Design and introduced "ubiquitous language" — the idea that developers and domain experts should share a single vocabulary. Not a programming language. Not business jargon. A shared language precise enough to write code in and clear enough to have a conversation in.

These two ideas — little languages and ubiquitous language — are usually taught as separate traditions. One comes from Unix hacker culture, the other from enterprise software design. But they're pointing at the same thing: the best tool for a domain is a language that speaks the domain's own words.

Two Traditions, One Idea

Four Words

The token language that drives our Petri net tools has exactly four terms:

Term Means Does
cell a place that holds things state container
func an action that changes things state transition
arrow a connection with direction flow and dependency
guard a condition that must be true constraint

That's the whole language. There is no fifth term.

This isn't minimalism for its own sake. Petri nets — the mathematical structure underneath — have been studied since 1962, and their expressive power comes from exactly this topology: places, transitions, arcs, and the conditions on them. Adding terms would add surface area without adding capability. Removing any would lose something essential.

The thesis is that these four terms constitute a ubiquitous language for modeling — not just Petri nets, but any system with state and transitions. The vocabulary is small enough to be little, precise enough to be formal, and natural enough to be shared.

Grammar, Not Vocabulary

But four words alone don't describe a system. The four terms are parts of speech — a grammar. Each implementation fills in its own nouns and verbs through labels.

In an ERC-20 token model: the cells are balances, totalSupply, allowances. The funcs are transfer, mint, burn. Those labels are the ubiquitous language of token standards — the words a financial engineer and a Solidity developer already share.

In a tic-tac-toe model: the cells are p0 through p8 — board positions. The funcs are play_X_0, play_O_4 — moves. The guards encode the rules: you can't play a taken square, you can't move after someone wins.

In the beats sequencer: the cells are kick_0, snare_3, hihat_5 — positions in a rhythm ring. The funcs are trigger, mute, unmute. The labels read like a drum machine manual.

Each model constructs a domain-specific vocabulary on top of a universal grammar. This is exactly what Evans meant by ubiquitous language — except instead of each project inventing its own ad hoc modeling conventions, they all share the same four parts of speech. The labels change. The grammar doesn't.

This distinction matters because the grammar is what the analysis tools understand. The tools don't care whether a cell is called balances or kick_0 — they see places, transitions, and arcs. Reachability, invariants, deadlock detection — all of it works regardless of what domain the labels come from. The labels make the model readable to humans. The grammar makes it analyzable by machines.

The Test: Can Everyone Use the Same Words?

Evans' criterion for a ubiquitous language is that it works in code and in conversation. If a developer says "cell" and a domain expert hears "state container," the language has failed. The words need to carry the same meaning on both sides.

With a shared grammar, the test becomes: can a domain expert label the cells and funcs, and can a developer read those labels as a spec? Here's what we've found:

Token standards. A financial engineer says "the balances cell feeds into the transfer func, guarded by sufficient funds." A Solidity developer reads the same sentence as a spec. The ERC-20 schema is seven lines — and it's simultaneously a model, a spec, and an executable test.

Games. A game designer says "each board position is a cell, each legal move is a func, the rules are guards." The model falls out directly. The designer names the cells; the developer wires the arrows. Same conversation, same words.

Music. A musician says "each instrument has a pattern — a ring of cells where tokens trigger notes, and control funcs mute or unmute voices." The beats sequencer works exactly this way. The control nets that wire instruments together are literally composition — in both the mathematical and the musical sense.

That last example deserves a pause. When control nets compose instruments into a song, we're seeing three layers of "composition" at once:

Three Meanings of Composition

The algebraic layer: the incidence matrix operates over integers — a ring — where token accumulation is additive and transition firing is multiplicative. The categorical layer: nets compose as morphisms in a symmetric monoidal category, wiring outputs to inputs. The musical layer: separate instrument nets combine into arrangements, and control nets sequence them into songs.

Same four words at every level. The language doesn't strain.

Why Four?

Bentley's little languages work because they're closed — you can't accidentally escape into general-purpose complexity. Regular expressions don't have loops. Makefiles don't have recursion. The constraint is the feature.

Four terms is the right number because of what you get and what you give up:

With four terms, we get:

With four terms, we give up:

That tradeoff is the whole point. We want a language that can't express everything — because the things it can't express are exactly the things that make systems hard to analyze. A Turing-complete DSL is just another programming language. A four-term DSL is a modeling language with mathematical guarantees.

Little Language, Not No Language

There's a common objection: "Why not just use Go? Or Python? Or YAML?" The answer is the same reason Bentley gave in 1986 — general-purpose languages are general-purpose problems.

Write a state machine in Go and you get a state machine. But you don't get analysis. You can't ask "is this deadlock-free?" without writing a model checker. You can't ask "what's the steady-state throughput?" without writing a simulator. You can't ask "does this conserve tokens?" without writing a theorem prover.

Write it in four terms and you get all of that for free, because the language is the model, and the model has sixty years of mathematical tooling behind it.

Evans made a related argument about ubiquitous language: if the language in the code diverges from the language in conversation, bugs hide in the gap. A state machine written in Go uses Go's vocabulary — switch, case, struct — which carries no domain meaning. A state machine written in cell, func, arrow, guard uses the domain's own words. The gap closes.

Composition All the Way Down

The word "composition" keeps showing up, and that's not an accident. Composition is what separates a language from a notation. A notation labels things. A language combines things.

The four-term DSL composes:

This is why the beats sequencer was the breakthrough use case. A drum machine is horizontal composition — parallel token rings. Song structure is vertical composition — control nets sequencing sections. The full track is categorical composition — everything wired into a coherent whole.

And the output is audible. We can hear whether the composition works. That's a feedback loop no unit test can match.

The Forth Connection

There's an earlier precedent worth naming: Forth, Charles Moore's stack-based language from the late 1960s.

Forth programmers don't write applications — they build up domain vocabularies. A Forth program for controlling a telescope doesn't look like Forth. It looks like telescope commands. Moore was practicing ubiquitous language decades before Evans gave it a name.

The parallels run deep. In Forth, composition is concatenation — f g means "do f, then do g." No syntax, no glue. The stack is visible state: values accumulate, words consume and produce them. Each word is essentially a transition that takes from the stack and puts back — there's a Petri net flavor to it, even if Moore never drew a circle-and-arrow diagram.

And Moore shared the same instinct for radical minimality. His later work (colorForth) stripped the language down even further. Not minimalism as aesthetic, but minimalism as capability.

The key difference: Forth is Turing-complete. You can build anything, which means you can't prove much. The four-term DSL deliberately stops short of that power — and that restraint is precisely why we get invariants, deadlock detection, and ODE analysis for free. Forth gives us the culture of little languages. The Petri net DSL gives us the guarantees.

The TLA+ Comparison

TLA+, Leslie Lamport's specification language, comes at the same problem from the formal methods side. TLA+ describes systems as states and transitions — exactly the territory Petri nets occupy. And it delivers similar guarantees: model checking finds deadlocks, liveness violations, and invariant failures before you write implementation code. Amazon has used it to find subtle bugs in S3 and DynamoDB that testing couldn't reach.

The difference is in the language itself. TLA+ is a mathematical notation — sets, logic, temporal operators. It's powerful and general, but it takes months to learn and the specs look like proofs, not domain descriptions. A financial engineer won't read a TLA+ spec and see their workflow. A game designer won't see their rules.

The four-term DSL makes the opposite trade. We give up TLA+'s generality — we can't specify arbitrary temporal properties — but we gain something TLA+ doesn't offer: a vocabulary that domain experts can read and write directly. cell, func, arrow, guard are words you can use in a meeting. TLA+'s \E x \in S : P(x) is not.

Both are little languages for verification. TLA+ is a little language for mathematicians proving distributed systems correct. The four-term DSL is a little language for practitioners modeling state machines they need to build. Different audiences, same underlying conviction: you need a language for this, not a library.

Moore, Bentley, Evans, and Lamport were all circling the same insight from different directions. The four-term DSL is what happens when you take all of them seriously at once.

The Thesis

Little languages work because they're small enough to fit in your head. Ubiquitous languages work because everyone shares the same words. The four-term Petri net DSL is both: small enough to learn in minutes, precise enough to generate code from, and natural enough that musicians, game designers, financial engineers, and developers can all use the same vocabulary.

The thesis is simply this: four terms — cell, func, arrow, guard — are sufficient to model any system with state and transitions, and the language they form is the ubiquitous language for state machines.

Not every system needs this. Not every domain maps cleanly. But for the surprisingly large class of problems that involve "things in states, changing according to rules" — which includes most of what we build software for — these four words are enough.

Further Reading

×

Follow on Mastodon