Programming Language Progress Is Subtraction

While re-reading Clean Architecture for my library notes, one sentence stopped me cold. Robert Martin defines the three programming paradigms, and he defines them backwards from every brochure ever printed:

"Each of the paradigms removes capabilities from the programmer. None of them adds new capabilities." (Clean Architecture, ch. 3)

Read it again. Seventy years of language progress, and the three greatest leaps forward are prohibitions. Nobody ever gave you anything. They confiscated your dangerous toys, one by one, and every confiscation made your code more predictable. Once you see the pattern, you see it everywhere, all the way into the languages of 2026. And it raises a dizzying question: what is left to take away?

1968: the first confiscation

The structured paradigm was born from a Dijkstra letter that became legend, "Go To Statement Considered Harmful". The goto was absolute freedom: jump anywhere in the program, anytime. And that was precisely the problem. Code riddled with gotos cannot be read, only investigated. You cannot reason about a block if anyone can land in it from anywhere.

The solution was not to add a feature. It was to ban the wild jump. What you are left with, if, while, for, is the tamed goto: control transfer that announces where it comes from and where it goes. You use the structured paradigm every day without knowing it, the way you speak in prose.

The next two paradigms follow the same pattern, and that is Martin's actual discovery:

  • object-oriented (Dahl & Nygaard, 1966) removes wild indirect jumps. In C, you could call "whatever code's address happens to be in this variable", with zero guarantee the address was any good. OO replaces that with the method: an indirect call whose target the compiler guarantees. Polymorphism is the function pointer, tamed;
  • functional (Church, 1936, before computers even existed) removes assignment: once created, a value never changes. And if nothing changes, whole families of bugs (shared state, concurrency) vanish with it.

Three paradigms, three removals, all discovered between 1958 and 1968. Martin draws a bet from it: there is nothing left to remove, there will be no fourth paradigm.

Timeline of removals: every language leap takes a capability away 1968 · Structured removes goto → code reads top to bottom 1966 · Object-oriented removes the wild indirect jump → compiler-guaranteed calls 1936 · Functional removes assignment → a value never changes again "there will be no fourth paradigm" (Martin, 2017) 2015 · Rust (ownership) removes shared mutation → data races no longer compile 2018 · Structured concurrency removes the wild spawn → no task outlives its block
The confiscation timeline. In green, Martin's three paradigms. In red, the two removals that contradict his bet: one already existed, the other arrived a year later.

The lost bet (and why that's good news)

The bet is printed in Clean Architecture, published in September 2017. And the spicy part: it was already losing when the book came out. One of the two counterexamples had existed for two years.

Rust (1.0 in May 2015) removed shared mutation: it is forbidden to have two places in the code mutating the same data at the same time. That is neither OO's removal (you can still make indirect calls) nor functional's (you can still mutate, just never two at once). The compiler rejects the program, and the entire category of data races disappears at compile time.

Structured concurrency, for its part, arrived one year after the book, and removed the wild spawn: it is forbidden to launch a background task that outlives the block that created it. Nathaniel J. Smith's founding essay (2018) is titled "Go Statement Considered Harmful", a deliberate echo of Dijkstra, because it is exactly the same crime: control flying off to who-knows-where, for who-knows-how-long. The unsupervised spawn is the goto of concurrency.

Martin was talking about sequential code, and on that narrow ground his inventory is probably complete. But his mechanics of progress, remove to make predictable, keeps producing. Right on the principle, wrong on the prophecy. That is the best kind of mistake: the one that confirms the theory while breaking the prediction.

Go, or the art of keeping only the marrow

If subtraction drives progress, then the most instructive language of the era is not the most powerful one, it is the most stubborn refuser. Rob Pike, Go's co-creator, summed up the language's philosophy in a 2012 talk whose title is the whole program: "Less is exponentially more".

Look at what Go refused: classes, inheritance, exceptions, operator overloading, annotations, and for ten years, generics. The list of absences caused screaming. But look at what it kept from OO: one single thing, the interface, that is, the compiler-guaranteed indirect call. And it even purified it: in Go, a type satisfies an interface automatically, without declaring it. Business code defines its interface at home, and the database package does not even know it exists.

// the business package declares its need, at home
type Saver interface {
    Save(invoice Invoice) error
}

// the mysql package imports NOTHING from the business code.
// It has a Save method: it satisfies the interface, period.
func (s *Store) Save(invoice Invoice) error { ... }

That is Clean Architecture's dependency inversion, no longer as a technique you apply but as the language's natural state. Go threw away OO's packaging and kept the only piece that mattered. Seventy years of OOP distilled into a mechanism that fits in ten lines of spec. If you want a closer look at that triage, I wrote up Learning Go in the library.

Those who removed more, and what it cost them

Go is not the end of the road. Other languages pushed subtraction further, and their trajectories draw the real limit of the exercise.

Zig removes hidden control flow. Its manifesto fits in one line: no exceptions, no overloading, no invisible allocation. You read a line, you know what it executes, all of what it executes. An error is a value the compiler forces you to handle, where Go still lets you write _ = err while whistling.

Elm removes undeclared effects: a function can neither mutate nor do sneaky I/O, everything goes through the return type. The famous, measurable result: no runtime exceptions in production. Probably the biggest reliability win ever bought by removal. And yet Elm remains a stagnating niche language: too pure, tiny ecosystem, the market ruled.

Erlang removes shared memory between tasks, since 1986: isolated processes passing messages, crashes that stay local. The whole industry spent thirty years rediscovering what its creators had figured out for telephone switches.

The emerging pattern: the first removals were free, nobody mourns the goto. The next ones cost. Rust buys the absence of data races at the price of a brutal learning curve. Elm buys the absence of crashes at the price of isolation. Go is probably a sociological local optimum: its real product is not purity, it is "any dev on the team reads anyone's code". Remove more, and you often lose that. The Pareto curve bends.

An Esperanto of programming?

Hence the question rattling in my head since that re-read: could an AI design the terminal language? The one that synthesizes every winning removal and keeps the essential unit bricks, the marrow and nothing else: mandatory errors-as-values, effects visible in signatures, native structured concurrency, zero hidden control flow. An Esperanto of programming: minimal, regular, no exceptions, no historical quirks.

The Esperanto analogy is tempting, and it teaches mostly through its failure. Esperanto is objectively simpler than every language it meant to replace. Regular grammar, zero irregular verbs, learnable ten times faster. It lost anyway, because a language does not win on intrinsic qualities: it wins on its ecosystem. You learn English for the people who already speak it, not for its grammar. Replace "people" with "libraries, tutorials, job offers, Stack Overflow answers" and you have exactly the gravity that keeps PHP, Java and JavaScript in orbit decades after "better" languages appeared.

But there is one parameter Esperanto never had: the primary speaker is changing. A growing share of code is written by AI, and for an AI, the cost calculation flips. Rust's brutal learning curve costs a model nothing. The library ecosystem weighs less when the generator can rewrite the missing brick. And every additional prohibition becomes a windfall: a language that rejects more programs at compile time is a better harness for generated code, every removal turns a class of plausible bugs into immediate compile errors. The removals "too expensive for humans" become profitable when the machine does the writing.

My honest forecast: the single universal language will not come, because domains pull in opposite directions (SQL removed the loop, shaders removed recursion, both were right, and neither can replace the other). What does seem plausible is a target language designed for the generated-code era, assembling the all-time best of subtractions. Not the Esperanto everyone speaks; the Latin machines would write to each other, checkable by the most intransigent compiler ever built. And we would review it, because by construction it would be boring to read. That is a compliment.

Conclusion

The thing this re-read changed in how I evaluate a tool: stop asking what it enables, ask what it forbids. The feature list is marketing; the prohibition list is engineering. A framework that can do anything protects you from nothing.

And the final irony deserves savoring: we are building the most powerful code generators in history, AIs capable of producing anything, and the best thing we can hand them is a language that refuses almost everything. Seventy years of progress by subtraction, only to understand that the most precious gift a language can give a programmer, human or not, is a compiler saying "no".

Comments (0)