A Minimal ML Core
Before regions, effects, four backends, or self-hosting, Mere had to be a language that actually runs. The smallest real core — Hindley–Milner inference, algebraic data types, pattern matching, a REPL — and why starting minimal matters, including how inferred types square with a language that prides itself on being explicit.
The ambitious parts of Mere — an explicit memory model, effects as capabilities, four backends, compiling itself in a browser — all attach to something. That something is a small, ordinary ML core, and building it first, before any of the interesting ideas, was a deliberate choice.
The smallest thing that is a real language
The first target was not impressive on paper: a language you can type into a
prompt and get an answer from. Literals and arithmetic, let, functions and
application, if. Enough to write let rec fact = fn n -> if n < 1 then 1 else n * fact (n - 1) in fact 6 and see 720.
The pipeline that produces that answer is the spine everything else grows on:
source → tokenize → parse (AST) → infer (types) → evaluate → REPL
Every later chapter is an extension of one of those stages. Regions and effects extend the types. The four backends replace “evaluate” with “generate code.” Self-hosting rewrites the whole spine in Mere itself. Getting the spine right and running — end to end, however small — came before making any of it ambitious.
Then came the features that turn a calculator into an ML: algebraic data
types and pattern matching, tuples, and polymorphic types — 'a option, lists, trees. These are the shapes the rest of the language is built
out of, so they had to exist early. A compiler that can’t represent and match
on its own data is a compiler that can’t grow.
Inference, in a language that prizes being explicit
There is a tension worth facing head-on. The first episode said Mere makes the
implicit explicit. This episode adds Hindley–Milner type inference — the
machinery that lets you write fn n -> n + 1 and have the compiler work out
int -> int without you saying so. Isn’t inferring types the opposite of
being explicit?
It isn’t, and the distinction is the point. Mere is explicit about the things
whose runtime representation or behavior can silently change — memory
ownership, effects, thread-crossing. It is not in the business of making you
write int on every binding. Ordinary types are reconstructed by inference;
they were never the part that hid dangerous behavior. The verbosity budget from
episode 1 is spent where it buys verifiability — the effects and the memory —
and not spent on annotations a machine can trivially recover.
So inference and explicitness are not in conflict; they are the same policy seen from two sides. Infer what is safe to infer. Surface what is not. HM gives the first half for free, which is exactly why it belongs in the core: it keeps the ordinary parts quiet so the explicit parts stand out.
Concretely, that means HM with let-polymorphism: a let-bound function is
generalized, so let id = fn x -> x can be used at int in one place and
str in another, each use instantiated fresh. This is table stakes for an
ML, and it is the foundation the later type-system work — regions, Send /
Sync — is bolted onto rather than fighting against.
Why minimal, and why first
Starting small is not modesty; it is method. The whole project runs on answering design questions by running real programs — dogfooding — and you cannot run anything without a working core. The order is forced:
- You cannot discover that your effect design is awkward in higher-order code until you can write higher-order code.
- You cannot find out that a region annotation is too noisy until you have regions attached to a language people (or you) actually write in.
- You cannot cross-validate four backends against each other until there is a language for all four to implement.
So the minimal core is the instrument the rest of the series is measured with. It is intentionally boring: a solid, unsurprising ML, the kind of thing whose correctness you can check by reading — which, per the first episode, is the whole point. The interesting decisions come after it exists, as answers to problems that only a running language can pose.
With that core in place — inference, data types, matching, a REPL you can poke at — the ambitious part begins. First up is the one Mere is arguably built around: how it handles memory.