Choosing a Host Language

A new language is written in an existing one, and that choice quietly decides which parts of the compiler are pleasant and which fight you. Why Mere is implemented in OCaml — what a compiler actually needs from its host, how the same 'make it checkable' principle applies to the compiler itself, and why the decision was made with a throwaway trial rather than assumed.

mereocamlcompilerlanguage-designhost-language

You do not build a language from nothing. It is written in some other language, and that host quietly decides which parts of the work are pleasant and which fight you the whole way. Before writing a line of Mere, the first real decision was: what do we build it in?

What a compiler actually needs from its host

Stripped down, a compiler is a program that reads a tree, walks it many times, and produces another tree or some text. The concrete demands are specific:

  • Represent an AST — dozens of node shapes, some recursive. This wants algebraic data types: Int of int, App of expr * expr, and so on.
  • Walk it, exhaustively — every pass (evaluation, type inference, code generation) is a big case analysis over those shapes. This wants pattern matching, and — crucially — a compiler that tells you when you forgot a case.
  • A tree-walking interpreter first — closures, environments, recursion. This wants first-class functions and a garbage collector, so the interpreter isn’t also a memory-management project.
  • Type inference — unification with mutable type variables, generalization, instantiation. This wants either cheap mutable references or good persistent data structures.
  • Diagnostics — every node carries a source position, threaded everywhere.
  • Several backends, later — emitting C, LLVM IR, and WebAssembly text. This is mostly disciplined string building, but it multiplies the case analysis, so exhaustiveness checking pays off again.

The same principle, one level up

The previous episode said Mere optimizes for the verifiability of correctness — being able to read a program and know it is right. That value does not only apply to programs written in Mere; it applies to the compiler for Mere just as much. A compiler is exactly the kind of program where a missed case or a mishandled node is a real, quiet bug.

So the host should be one where those bugs are hard to hide. A language with algebraic data types and exhaustive pattern matching turns “you forgot to handle record types in the code generator” from a runtime surprise into a compile error. Adding a new AST node makes every incomplete pass light up. The host’s own type system becomes a tool for keeping the compiler honest — the same bet as Mere makes, applied to Mere’s own implementation.

Why OCaml

OCaml fits that shape almost point for point:

  • Algebraic data types and pattern matching are first-class and ergonomic — the AST and its passes read the way you’d sketch them on paper.
  • Non-exhaustive matches are warnings you can treat as errors, so the “did you handle every node?” check is free and continuous.
  • A garbage collector, so the interpreter can be a tree walk and nothing more.
  • A mature toolchain (dune, readable type errors) that stays out of the way.
  • And it is ML-family — which matters twice, because Mere is itself an ML-family language. The idioms you use to write the compiler are the same ones the language is about, so intuition transfers in both directions.

There are reasonable alternatives. A systems language like Rust would give more control and speed, but a compiler is full of tree- and graph-shaped code with lots of sharing and mutation, and fighting an ownership checker for the host program is friction spent in the wrong place — Mere’s own explicit-memory story is the point of the project, not something to also pay for while building it. (The host using a GC while the language it builds does not is not a contradiction; it is separation of concerns.)

Decided by trial, not by assumption

The recurring method in this project is: don’t assume, run a small trial first. The host choice was the first place it applied. Rather than commit to OCaml on vibes, the decision came from a throwaway prototype — a tiny expression language implemented end to end in OCaml — built specifically to check that every demanding piece is actually natural:

  • an ADT AST and recursive evaluation,
  • a small hand-written parser,
  • closures and environments,
  • source positions threaded through,
  • and even bidirectional type checking, to make sure the type-inference machinery wouldn’t be awkward.

Only once that prototype confirmed each piece was comfortable — carried by a few dozen tests — was OCaml committed to as the host. The trial is throwaway on purpose: its job is to answer a question, not to become the codebase.

That is the shape of nearly every decision that follows in this series. A design question is filed, a small trial or paper sketch answers it, and only then does it become real code. The host language just happened to be the first question on the list.

Next: the minimal core — the smallest version of Mere that actually runs.

← Back to Mere: Building a Language