↓Skip to main content
  1. Posts/
  2. Series/

🐫 Songs of the OCaml Compiler: The Series

I recently found myself with some time, so I dedicated 2 weeks to pick up OCaml’s basic patterns-of-thought by writing an event-simulator that demonstrates a classic consensus protocol (Paxos). The goal was to implement something familiar as a proxy to gaining intuition around something unfamiliar. ~4 weeks later Scope-creep is inevitable for personal projects. This also doesn’t count the months between finishing the code and finishing the writing — some consulting work intervened, and the series sat until I could give it the attention it needed. The open git history chronicles the actual timeline. , the project is at a stage where I can focus on talking about it…

This series documents the design thinking, decisions made and moments of friction as a story-arc where my relationship with the compiler changes from adversarial to collaborative. What starts as noise — type errors that I couldn’t explain, constraints that felt arbitrary — eventually becomes something I could work with. Towards the end of the arc, I even find myself applying the compiler’s discipline in places where it has nothing to say.

Happily, this work is also a small step towards my long-term goal of creating meaningful teaching artefacts (e.g. guided projects with effortful learning curves) for FOSS ecosystems. Teaching people how to teach themselves is difficult because learning is a deeply personal habit and not every skill is learned the same, but a reliable start to exploring that is to make my own learning style visible.

Figure 1: 🙙 Photo of the Milky Way seen from the Gobi desert by Daniel Kordan, retrieved 🙛

What to Expect #

In this series, we’ll first spend some time world-building so that we can bake algorithm invariants and helpful assumptions into narrative analogues that will help guide our understanding. Consensus algorithms are famously difficult to learn, so we shall pick the simplest version of the Paxos protocol and its safety guarantees for the first cut of the simulator. This helps to maintain focus on what the language and its tooling can teach us. Before we write any code, we will establish some design grammar that shapes how we architect our simulator. Finally, we cover some anecdotes on how the compiler influenced my own implementation.

Here’s the gist of what to expect:

A Tuareg-themed Narrative Backdrop #

To explain his consensus algorithm, Leslie Lamport1 chose the narrative backdrop of part-time parliamentarians on the ancient Greek island of Paxos. Since this is an OCaml-project, we keep things Camel-themed and draw inspiration from the nomadic Tuaregs that travelled across the Saharan desert for trade.

Part I starts with building that world: where multiple caravan groups have to come to a decision — a consensus — despite unreliable environments that may impede communication.

Table 1: The Paxos- and Tuareg-themes are analogous
Paxos-themedTuareg-themedStructure
Parliamentarians with private opinionsCaravans with private commitmentsActors with local state
Messengers between parliamentariansCouriers shuttling parchmentMessages between nodes
Senate sessions convening & adjourningDesert sun rising and fallingLogical time
Scribe recording the decreesChronicler observing from afarSimulator interface

To keep the focus on the safety-argument and simplify the implementation-scope, we will tactically omit some behaviours from this v0 implementation:

  1. Single decision (consensus attempt), not a replicated log

    We will prioritise demonstrating safety within a single decision so that per-node state is small enough to trace by hand. Nodes will keep an append-only log but we’ll defer log-replication to v1.

  2. No failure-detection

    For example, we won’t use timeouts to detect failures because detection as a goal is not important for Paxos’ safety argument. This also removes timing as a source of non-determinism.

  3. No log-reconciliation

    Nodes will crash and restart, and they will never lose state. However this durability of state will be basic since we won’t build the ability for nodes to bring laggards up to speed i.e. log-reconciliation.

  4. Modelled concurrency

    Time will be modelled deterministically as a single-threaded loop. Concurrency would be modelled as an interleaving of events for v0 so that everything is easy to trace.

The original paper proves safety rigorously but deliberately leaves progress mechanisms unspecified — treating those as implementation choices rather than protocol requirements. Our omissions here extend that same instinct: v0 keeps the safety argument airtight and defers everything else.

The narrative backdrop should provide sufficient context to appreciate the need for consensus protocols and how to achieve safety. Details on the fundamentals of Consensus Algorithms, deeper literature and some commentary are in the Addendum.

Design Grammar Before Any Code #

We complete Part I without writing a single line of code because the project’s objectives and design constraints should be influencing the implementation rather than the language constructs OCaml’s powerful type system allows users to do sophisticated type-magic to fit their need for expression. It feels good to wield but also makes it easy to overdo it — that’s what we discover in Part II when we try to enforce state machine invariants and have to curb our enthusiasm. that we are free to play with.

The design rules we establish make us disciplined when separating concerns so that there are clear evolution-paths beyond the v0 implementation of our simulator.

Table 2: The grammar rules that shaped the architecture before any code did
RuleWhyPossible to evolve to
1. Nodes talk to each other only by exchanging messagesMessage-passing is the only way to interact and an instance of a Bus becomes the single channel for communication. We can trace cause-and-effect by watching a single queue instead of chasing multiple call-stacks.Supporting a real transport protocol (e.g. IP) requires touching one module instead of every actor.
2. Time moves in discrete, inspectable stepsEvery “tick” is loggable, diff-inspectable, and replayable. We don’t have to fight sources of nondeterminism such as the wall-clock.A wall-clock or multi-threaded driver can be dropped in later without touching actor logic
3. Events are deterministic and replayableEvery run reproduces from its inputs, so that bugs in the simulator implementation are easy to squash by avoiding any race conditions.Opens the door to record/replay testing and, eventually, fuzzing over event sequences. We can be confidently chaotic, later.
4. Actors are independent. No actor reaches into anyone else’s stateEncapsulation is enforced by the module boundary so that a leak of control responsibilities can never be compiled.New roles can be added, or one role split into several, without auditing every existing actor for hidden coupling.
5. The world can be paused, rewound, and fast-forwardedForces simulator state to be a value, instead of keeping a history of mutations.Sets up a debugger/inspector, and property-based tests that make simulator behaviour easy to trace.

We expect our implementation to be shaped by our design grammar more so than the OCaml language.

Pressures from the Compiler #

Part II implements the grammar and hits three places where the compiler pushes back.

The setup for all three: a node wears three hats at once — Proposer, Acceptor, Learner — each its own sub-state machine, all live simultaneously. That’s what turns the type questions below into real design decisions rather than syntax trivia.

Table 3: Three movements in the relationship with the compiler
MovementThe pressureWhat it taught
ObeyingWhen wiring the subsystems together, the compiler refused to unify two types that looked identical to me — the first of two module-boundary snags. The second snag cost more: in order to satisfy a signature that I’d defined too rigidly, I’d been carrying converter functions that shuffled identical fields back and forth.A module signature is an access-control boundary, but I was treating it more like a type hint. Making the type abstract in the signature let the equality be induced rather than declared. The boilerplate evaporated on its own.
NegotiatingTo make illegal state transitions unrepresentable, I reached for a GADT. However those same state types carry [@@deriving sexp, yojson] preprocessor tags so that every transition can be snapshotted — and ppx deriving assumes every constructor returns one uniform type, which is exactly what a GADT refuses to do.The more type-safe the runtime model gets, the less safe the serialisation boundary gets. Replayability is the point of a simulator, so the failwith arms stayed — known, bounded debt with a resolution already sketched out.
InternalisingMovement 2’s restructuring had a side effect: layering state by role gave each sub-state its own accumulator, which quietly made the old message-inbox redundant. With the inbox gone, every side-effect converged on one function. That was emergent behaviour, I hadn’t designed it like that.A mutation boundary appeared with no compiler forcing it. The discipline held anyway, because the compiler’s habits of attention — shape of data, direction of flow, where mutation is allowed to live — felt intuitive to me at that point.

The compiler sings, you learn the songs, and eventually you find yourself humming them in places it isn’t.

Three Parts to a Whole #

Each part is self-contained. Reading in order (Part I → II → III) gives the full arc, but entering at any point should work alright too.

PartWhat it coversStatus
I: A Proxy ProjectNarrative backdrop, design forces, abstract grammar, constraints before constructs. Personal narrative on linguistic relativity and what programming languages teach us. No code.Published
II: A Design TourThe most technically involved part. Three design pressures with real compiler errors, architectural diagrams, and code that evolves in front of you.Published, revising
III: coming soon, untitledEvolution paths, honest debt ledger, and what OCaml leaves behind in the way you think about any codebase — even ones not written in it.Planned
AddendumMy observations and intuition on some core concepts revolving around this space that may help fast-track other learners. It also aims to funnel readers to actual canonical work that others have done.Recently revised
CodeThe implementation.latest relevant tag: v0-simple-paxos

Parts I and II are complete, while III is in the drafting room for a while more. If you’d rather start with some consensus fundamentals first, then the Addendum is self-standing for that.

Suggested Reading Paths

Different strokes for different folks: people of different profiles may prefer their own way to read this series:

If you are \(\ldots\)\(\ldots\) you care aboutthen try starting with \(\ldots\)
someone interested in how engineers learn and make design decisions — pedagogues, mentors, the curious — even if you don’t consider yourself a technical personthe why behind design: forces, analogies, constraintsPart I — no code, all design thinking. Wanders into linguistics, learning science, and the cognitive residues that programming languages leave behind.
an experienced programmer outside the OCaml world — you build systems, you’ve used a few languages, you want the architectural substance juxtaposed with the code and see it in a seemingly esoteric languagethe how: trade-offs, type-system lessons, cross-language perspectivesPart II’s three design pressures — opens with enough context to stand alone. Part I’s Abstract Design Grammar is worth a look if the design constraints intrigue you.
someone that works in or around the OCaml ecosystem — you’re evaluating the engineering, the taste, the self-awarenessthe what: implementation quality, honest debt, maturity of judgementPart II directly, focusing on the design pressures. Part III’s debt ledger will be the most candid section about where the architecture falls short.
someone interested in the distributed systems space, especially the consensus contentthe substance: quorum intersection, where the variants actually differ, and what a v0 does and doesn’t guaranteethe Addendum — it’s the depth-layer and reads standalone. Part II if you want to see the protocol as it was built.
If you have feedback or ideas on making this series clearer, more demonstrative, or more useful — please reach out to me or open an issue here.

  1. The legendary Leslie Lamport laid the foundations for managing chaos in distributed systems. The Paxos algorithm was published in the 90’s as “The Part-Time Parliament”, which built on his earlier work around the space. ↩︎

2026


🐫 Addendum: Consensus & More

Paxos is used as a learning substrate Paxos is hard to understand and harder to implement right. This project keeps the scope to single-decree Paxos — narrow enough to be tractable (~2 weeks) and deterministic enough to verify cleanly. in this series. This addendum hosts my observations and intuitions on some core concepts revolving around this space that may help fast-track other learners. It also aims to funnel readers to actual canonical work that others have done.

·· 6648 words· 27–45 min read

🐫 Part II: A Design Tour

This is Part II of a series on learning OCaml by writing a Paxos simulator. We build on Part I’s abstract grammar and witness how subsystems take shape from it — guided, as it turns out, by the OCaml compiler I’m sure the magical source within the compiler is rooted in the underlying Hindley-Milner Type system which expects the programmer to exercise clearer type-discipline in exchange for superior inference capabilities that feel like an extension to the programmer’s own mind. I’ll put up a few words about what that experience has been like in Part III, coming soon itself.

·· 8991 words· 36–60 min read

2025


🐫 Part I: A Proxy Project

This is Part I of a series on learning OCaml through building a Paxos simulator. Here, we stay away from the code entirely and focus on listening closely to the forces that will shape the architecture.

·· 4901 words· 20–33 min read