The Other Half of Ordering
The self-hosting fixpoint was a summit, but a summit is a place you leave. The first step down was to pay a debt named out loud several parts earlier: the language could compare any two values for structural equality, but it could not order them. Finishing that — a structural less-than to sit beside the structural equals — completed a family of behaviours the language had been growing without ever building a trait system, and it did so by reusing the exact machinery that family was already made of.
A summit is not a destination; it is a place you turn around and walk down from. The previous part ended at one — the bootstrap fixpoint, the compiler compiling itself into a byte-identical copy — and this part is about the ground on the far side of it, where the work stopped being about the compiler proving things about itself and went back to being about what the language could and couldn’t yet do. The first step down was small and specific, and it had been waiting, named, since a much earlier part.
A debt named out loud
Parts ago, when a family of derived behaviours was built — rendering a value as text, serialising it to JSON and back, comparing two values for structural equality, all specialised per type at compile time with no trait system anywhere — one member was described plainly as missing. The language could ask whether two values were equal by walking their shape; it could not ask which one was smaller. There was structural equality and no structural ordering. A numeric tool built later had bumped into the gap directly: it could sort a list only by spelling out a comparison by hand, because the comparison operators worked on numbers and strings but not on the tuples and records a real dataset is made of. The edge was left in the open, written down as unbuilt rather than hidden, because the discipline of the whole project was to defer a thing until a program made its absence concrete — and now one had.
Finishing the family, from the family’s own parts
The satisfying part is that finishing it required no new mechanism. The equality it was missing a partner for was not implemented with a trait or a dictionary or a runtime lookup; it was implemented by the type checker knowing the concrete type at each use and the code generator emitting the right comparison for that type, inlined, at compile time. Ordering slots into exactly the same frame. Less-than, and its three relatives, now work on any concrete type: integers and floating-point numbers and strings compare directly, and tuples, records, lists, and variants compare lexicographically — component by component, field by field in declared order, element by element, and for variants by their declaration order first and their contents second. Each is emitted as a small compare function per type, the ordering sibling of the equality function that was already being emitted. One name, many type-specific behaviours, chosen statically — the same shape the render and the JSON and the equality had, now with the comparison completed.
Making the backends agree on what “smaller” means
The one genuinely new decision was what order to impose where the language doesn’t hand
you one. Numbers and strings have an obvious order; a variant type does not. Red | Green | Blue has no inherent ranking, so a choice has to be made and — this is the part that
matters — the interpreter and both compiled backends have to make the same choice, or a
value would sort one way under the interpreter and another way as a native binary. The
order chosen is declaration order: the constructor written first is smallest. It is a
choice, not a discovery, but it is a total, deterministic one, and the interpreter
reconstructs it from the same source the code generators use to number the constructors,
so all three agree byte for byte. That agreement is checked the way the whole project
checks such things — the same value sorted three ways and required to come out identical —
rather than asserted.
The edge that stayed
Completing the family did not close everything, and the honest thing was to say which corner remained. A comparison written without type annotations still defaults its operands to integers, so the convenient one-liner sort — the one that takes no comparator at all — is still integer-only; sorting a list of anything else wants an annotated comparison spelt out. The fully general version, where the language infers “orderable” as a constraint and resolves it wherever the element type lands, is a different and larger machine — real ad-hoc-polymorphism resolution — and it stays deferred behind the same rule as before: until a program makes its absence unbearable. What shipped is the structural comparison itself, working on every concrete type across every backend, which is most of the value and none of the new machinery.
This was a deliberately modest first step off the summit — a debt repaid, a family completed, a promise from an earlier part kept in the open. What came next was less modest. Having finished tidying what the language already had, the work turned outward, to the things it could not do at all — and the way to find those was not to guess, but to go looking for the places building something real would hurt.