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.

merefloatsdocumentationbackendslanguage-design

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.

← Back to Mere: Building a Language