David Brandt · trains of thought

Programming languages · The lowest common denominator, part 4 of 4

The cost, then and now

Why LCD would have taken years, and why that reason is mostly gone

Seeded 2001 Written November 2026

I dropped LCD because it wasn't something I wanted to spend my time on, and because in those days it would have taken a massive amount of time.

Twenty-five years later I can be more specific about the second half of that.

The bill

A transpiler for every target. C, C++ once for each framework, Java, PL/SQL, Pascal, and COBOL if it was ever going to reach the older systems. Each one emitting code that reads as if a person wrote it, which is much harder than emitting code that merely works.

A runtime library for every target. Decimal arithmetic, every rounding mode, dates, text and collections, each written by hand in the target's own language. C needs all of it. PL/SQL needs most of it. Each C++ framework needs its own glue.

Levels multiply the work. Every level has to be defined, tested and transpiled for every target that carries it, and a change to one level has to be checked against all of them.

The gaps. I never got as far as deciding how to close the gaps between targets, and each one is real work. Typing: PL/SQL's NUMBER against fixed-width integers, Java without generics, C without checked anything, Perl without static types at all. Memory: values that are cheap on every memory manager, and shared references that behave the same under a garbage collector and a reference counter. Data: the database thinks in rows and sets, and everyone else thinks in records, so a loop that is natural in Java is slow inside Oracle. Text: the database, the JVM, Windows code pages and C's bytes each had their own idea of a character, so there was no single meaning of "length" or "upper case" to transpile.

Proving the targets agree. The test suite would run on every toolchain, some of them inside a database, and every disagreement would have to be traced to a transpiler, a runtime library or the definition of a level.

Keeping up. Every new version of a target changes what a level can hold. Java 5 arrived in 2004 with generics and enums. Oracle 10g added new numeric types. Each framework kept moving too.

Adoption. Even when it all worked, the generated code would be code that a team in each environment had to debug, deploy and own, written by a tool they hadn't asked for.

None of that is impossible. It was just years of one person's work for a tool whose benefit arrived only at the very end, once every transpiler was finished. I had a job, and other things I would rather do. It stayed a dream.

It came back anyway

What surprised me, going back through everything that led to Y, is how much of LCD's thinking kept turning up without my ever having written LCD down.

Transpiling. YAPL, the first version that left any written trace, was started in 2019 and early on described itself as a transpiler to "all machines". In the 2024 design discussions I gave it up: "we are no longer aiming for transpilation". In September 2026 it came straight back, in a conversation that started with a language that "really just transpiles to Rust". Y's position today is a compromise I think LCD would have recognized. Rust is not Y's definition; it is a backend and a check. But the example programs are lowered to plain, readable Rust that an ordinary Rust toolchain builds.

Levels. Y has a constraint language for describing rules about code that lives in other languages: Python, T-SQL, C#, Rust and more. Its kinds of rule are switched on host by host, the simpler kinds for some languages and the richer ones only for languages whose types can carry them. That is LCD's levels again, with "level" renamed to "kind".

Rules written once. The reason that constraint language exists is the problem LCD started from: business rules repeated across a client, a server and a database, each copy free to drift.

The host does what the language can't. Y has no foreign-function interface. When it needs something it can't express, the function is declared in Y and written in the backend's own language. LCD's runtime library, written by hand in each target, was the same move.

The same tests everywhere. In Y, running several backends and comparing them is a way of finding bugs, and a disagreement is a bug, never a vote. That is LCD's test suite, almost word for word.

And the small things. No / on integers; division says how it rounds. No null, and nothing is true except a boolean. Every name lowercase, with underscores. Every decision named, so traces can be compared. Every function declaring how it can fail. No clock and no randomness unless they are handed in. Every rule saying why. Each of those appears in Y, and each of them is an answer to a question LCD would have had to ask.

Most of them first turn up in writing years later, in YAPL or in the 2024 design discussions. All of them are what you would end up with if you spent a long time trying to make several languages agree, and that is what I spent some of 2001 thinking about.

What changed

LCD was a set of small languages for people to write, with transpilers written by people, each level chosen so that its targets could carry it. Y is a whole language, written mostly by AI agents and reviewed by people, with a compiler that checks what the agents propose. I wrote about what happens to a language when its writer changes in Write-only languages.

The change that matters for LCD is simpler than that. Look at the bill again. A transpiler per target. A runtime library per target. A test suite run against every target, with every disagreement traced to its cause. That is exactly the kind of work that is specified once, checked mechanically, and done many times over: the kind of work agents now do well.

The reason I dropped LCD is mostly gone. The question it asked is still the one Y asks: how do you write a rule once, and trust it wherever it runs?

David Brandt is a computer scientist, systems architect and independent scholar based in Reykjavík.