The Picture That Fixed the Docs
A Mandelbrot renderer went in to measure the tax the docs promised: no infix float operators, so every formula would be a pile of prefix calls. The docs were wrong in the language's favor — infix arithmetic on floats had worked for a while on three backends, and a doc-faithful reader was writing nine needless prefix calls per line for nothing. Three real gaps hid around the stale sentence, and the picture that came out the far end was a correct one, byte-identical whether written as raw pixels or ASCII.
Most of this part is about the language being narrower than it looked. This episode is the one where it was wider — where the documentation described a poorer language than the one that existed, and a probe written to measure a limitation found the limitation had quietly lifted.
Measuring a tax that wasn’t there
The plan was a Mandelbrot renderer, chosen precisely because it is float-arithmetic all
the way down, to quantify the tax the reference described: floats had “prefix function
style only, no infix” — every a*b + c would have to be written as nested prefix calls,
and the escape-time formula would balloon. The very first line of the renderer wanted a
negative float literal and got a type error, which felt like confirmation. Then, checking
something unrelated, 1.5 + 2.0 returned 3.5. Infix + on floats worked. So did -,
*, /, and every comparison — on the interpreter, on C, on Wasm. The overloading had
been added at some point and the reference had never been updated; it still promised the
prefix-only world, and a reader who trusted it would write nine needless prefix calls in
a three-line formula for no reason at all. This is the mirror image of the “it already
existed” episodes from the last part: there, a probe found a feature it meant to build
was already present. Here, a probe found a feature the docs denied was already present.
The documentation can drift in both directions, and only running the language tells you
which way.
The three real gaps, and the honest wrinkle
The stale sentence was hiding three genuine gaps, all fixed. Unary minus was still
integer-only — -2.5 really was a type error, and negative float literals needed a named
negation function — so it became numerically overloaded like the binary operators, on all
four backends. The LLVM backend, it turned out, had been emitting integer-add
instructions on floating-point operands for infix float arithmetic — invalid IR that the
compiler rejected outright, and integer comparisons for float comparisons — so it got the
proper floating-point add family and ordered floating-point comparisons. And the write
half of the binary story from last episode was missing: there was a byte-vector reader
but no writer, so the renderer got a write_file_bytes to match, which let it emit the
raw-pixel form of the image format instead of the ASCII escape hatch — the raw form is
about a third the size. The renderer draws a 400×300 set in plain infix math, and its
raw-pixel output is byte-identical to the ASCII version’s pixels. One honest wrinkle
stays and is written into the corrected docs: the overload resolves to float only when an
operand is concretely float, so an unannotated function parameter still defaults to
integer, and float-heavy code annotates its parameters. The correction went into both the
reference and the standard-library notes. The lesson pairs with the whole part: four
backends can disagree with each other, and the docs can disagree with all four at once.
The last episode of the part fixes a papercut that two of these probes had to work
around — and then closes the ledger on the day.