Change the Domain to Find New Pain
Four dogfood applications had all lived in the same country — string and record work, bound by input and output — and they had stopped turning up new kinds of pain. So the next one was deliberately numeric: a statistics tool over CSV columns. It found a whole new class of friction immediately, the sharpest being that floating-point numbers had no infix operators at all. The fix reused the very machinery the JSON family was built from, one collision recalled an earlier fix by name, and the arc closed on an edge left honestly open — the ordering half of ad-hoc polymorphism, still unbuilt.
By now the dogfood method had a weakness worth naming. Four applications had been built to find the language’s rough edges — a web-and-database app, a realtime notes server, a command-line query tool, an awk-like evaluator — and they had all lived in the same country. Every one of them was string and record work, bound by input and output, its arithmetic incidental. They had been thorough about the pains of that country, and they had gone quiet, because a program can only surface the pain its own shape touches. The way to find new pain was not to build a fifth application like the four; it was to move to a different domain entirely, and the obvious unexplored one was numbers.
The tax nobody had paid
So the fifth dogfood was a statistics tool: read a column of numbers out of a CSV file and report count, mean, median, minimum, maximum, standard deviation. And it hit the new country’s border almost immediately. Floating-point numbers, it turned out, had no infix operators at all. Addition and the rest were prefix function calls, and the comparison operators were defined only on integers, so a comparison of two floats was a type error. The numeric core of the tool, the part that should have read like arithmetic, instead read like a sequence of function applications — the running sum was a call, not a plus. This is the single largest ergonomic tax a numeric program can be asked to pay, and it had gone entirely unnoticed, because none of the four earlier applications had done enough arithmetic on fractional numbers to feel it. The pain had always been there; no program had ever stood in the right place to be hurt by it.
Extending the family, once more
The fix did not call for new machinery, and that is the part worth dwelling on. Making the operators work on floating-point numbers used exactly the mechanism an earlier part had built for rendering and JSON and equality: overload the operator, and let the code generator choose the right implementation from the operand’s type at compile time, with no runtime dispatch and no trait declarations. The arithmetic operators simply became new members of the same compile-time-specialised family — one name, the integer behaviour or the floating-point behaviour selected by the type that was already known. Bringing them in even flushed out a latent bug hiding underneath: a whole-valued floating-point literal had been printing in a form that made the compiler read it back as an integer, so a division of two such literals had silently been integer division. The numeric tool’s core, once the operators landed, read as ordinary arithmetic with not a single prefix call left in it.
The collision that had a family
One friction arrived already solved in spirit, because it belonged to a family the series had met before. A helper function named the same as a standard math-library function collided with it when compiled, the C compiler refusing two conflicting definitions of one name — the very shape of the shadowing problem the command-line tool had run into with a threading primitive, one country ago. The remedy was the same: teach the backend that these library names, too, must yield to a program’s own definitions and be renamed out of the way. A recurring family of bugs is not a discouraging thing to find. It means the fixes are becoming a pattern, and a pattern is cheaper to apply the second time.
The edge left open, on purpose
The most honest part of the arc is the pain it did not fully fix. Sorting a list is the natural next thing a statistics tool wants, and the convenient default sort was defined in terms of the comparison operator — which now worked on floats, but only when the values were concretely typed. The default sort had baked in an integer comparison at the point it was written, so sorting a list of floats still required spelling out an annotated comparison by hand. Underneath this is the genuinely unfinished frontier: the language has a structural equality that works on any type by walking its shape, but it has no structural ordering to match — no derived less-than for records and tuples the way there is a derived equality. That is the ordering half of ad-hoc polymorphism, and it is real, and it is not built. Naming it plainly, as a known edge rather than a hidden one, is the only honest way to leave it — and leaving it, rather than rushing a design, is consistent with everything the series has done: the feature waits for the program that makes its absence unbearable, and a statistics tool that can sort with one extra annotation is not yet that program.
What the fifth country taught
The lesson of the whole exercise is a fact about the method itself. The pain a language has left to give is a function of the programs written against it, and four programs from the same domain will eventually agree that the language is fine — not because it is, but because they can no longer feel what they never touch. Moving to numbers made the language speak up again on the first day, and the things it said were things four string tools could never have said. It also left a clean, named question on the table — a structural ordering to sit beside the structural equality — which is exactly the posture a living language should be in. The dogfood loop had now been run in five domains and had earned its rest, because the next thing the language had left to prove was not about any application at all. It was about the compiler itself, and whether the thing that had compiled all five of these could, at last, correctly compile the one program it had never truly run: itself.