Daniel's been digging into the history of programming languages, but he's not interested in a timeline. He wants to know how the actual relationship between a language and the hardware underneath has changed over time — not just which languages came when, but how the whole way we construct languages to talk to a machine has evolved. He points out the history of programming is barely a century old, and the evolution of that relationship is arguably the more interesting story. So we're going from plugboards to JIT compilers, and the question running through all of it is: who's making the decisions about the hardware, and when did that change?
This is one of those questions where the surface is straightforward and the underneath is... not. Because what does it actually mean for a language to relate to the underlying compute?
Right. It's not just distance from the metal.
No. It's the contract. It's who decides what happens in the silicon. At one end of history, the programmer decides everything — every instruction, every register. At the other end, the runtime watches the hardware in real time and makes decisions the programmer never even knows about. And the whole story is this slow, uneven transfer of authority from human to intermediary.
And the intermediaries got smarter than us along the way.
They did. There's a useful framework for tracking this — the generations of programming languages. It's imperfect, the boundaries are fuzzy, but it captures something real. First generation is machine code. Second is assembly. Third is high-level compiled — FORTRAN, C. Fourth is declarative, closer to human intent than machine. Fifth is logic and constraint-based — Prolog. Each generation moves the language further from the machine's perspective and closer to the human's.
But the framework makes it sound like a clean staircase, and it wasn't.
Absolutely not. But the framework is useful because it reveals the through-line: every generation was an answer to the pain of the previous generation's relationship with the hardware. Nobody sat down and said let's invent a new generation. They said I cannot spend another day doing this by hand.
So start with the thing nobody wanted to do by hand.
Before languages, there were plugboards. Physical rewiring. You wanted the machine to do something different, you got out the cables and replugged the board. The machine was the program — there was no distinction. ENIAC, nineteen forty-five, programmed by rewiring patch panels. It took days to change the program.
Which means the relationship to compute was... direct in a way that's almost incomprehensible now. You touched the thing.
You touched the thing. And then machine code — binary instructions fed directly to the CPU, no intermediary at all. You're writing in the instruction set of the specific processor. Move this value to this register, add these two, store the result there. Every instruction is a binary pattern the hardware recognizes natively.
And the programmer has to know the exact instruction set of that specific machine.
Every opcode. Every addressing mode. And if you moved to a different machine, you started over. The relationship was completely transparent — you could see exactly what the hardware was doing because you specified every single thing it did. But transparent doesn't mean easy.
Transparent means you see all the way through, and what you see is pain.
That's the line of the episode so far. So the first real leap was assembly language, late nineteen forties into the fifties. Mnemonic symbols mapped one-to-one to machine instructions. Instead of writing a binary pattern for move, you write MOV. The assembler translates it directly — one assembly instruction, one machine instruction.
So the assembler is not making decisions. It's a dictionary.
Pure translator. No optimization, no judgment. The programmer still thinks in terms of registers and memory addresses and instruction sequences. The relationship to compute hasn't changed qualitatively — you're still in full control. What changed is that you no longer have to memorize binary opcodes. It's a human factors improvement, not a structural one.
But it's the first intermediary.
It's the first intermediary. And that's important because it establishes the pattern — a program that takes your human-readable input and produces machine code. The assembler proved the concept. The question was whether an intermediary could do more than just translate.
Enter FORTRAN.
Nineteen fifty-seven. John Backus and his team at IBM. And this was the watershed. FORTRAN wasn't just a translator — it was a compiler that made optimization decisions. You wrote mathematical formulas in something close to standard notation, and the compiler produced efficient machine code. Sometimes more efficient than what a skilled assembly programmer would write.
That was the controversial part.
Hugely controversial. The skepticism was deep and reasonable. How could a machine-generated program possibly be as efficient as hand-written assembly? The programmer knows the specific problem, the specific data, the specific hardware. The compiler knows... a set of general rules. There was a real belief that compilation would always carry a performance tax, and that tax would be unacceptable for serious work.
And that belief shaped compiler design for decades. It still echoes.
It does. You still hear people say they write C because they want to be close to the metal. The ghost of that skepticism is still in the room. But here's what happened: within a few years, FORTRAN compilers were producing code that rivaled hand-written assembly. The intermediary became trustworthy.
What made the difference?
The compiler could do things a human couldn't do consistently. It could analyze the entire program's structure and make global optimization decisions — register allocation across hundreds of variables, loop unrolling, instruction reordering. A human assembly programmer can do that for small programs, but for large ones, the cognitive load is crushing. The compiler never gets tired and never forgets a constraint.
So the compiler wasn't just translating — it was understanding the program well enough to make better choices than the programmer about the hardware.
That's the shift. That's the moment the relationship changed qualitatively. Before FORTRAN, the language was a thin skin over the machine. After FORTRAN, the language was an active participant in deciding how the machine would be used. The programmer said what to compute, and the compiler figured out how.
And this is where the abstraction ladder starts getting built in earnest.
Right. COBOL in nineteen fifty-nine pushed in a different direction — toward human domain language. Business English. The idea was that a manager should be able to read the code and understand what it does. It wasn't about mathematical notation, it was about readability for non-programmers. The relationship to compute became mediated by a language designed for human communication, not machine efficiency.
Which is a different kind of abstraction entirely.
Completely different. FORTRAN abstracted the math. COBOL abstracted the intent. And then ALGOL, nineteen fifty-eight to sixty, formalized the whole theory of language design. Block structure, nested functions, and — this is crucial — the idea that a language could be defined by a formal grammar. Backus-Naur Form, BNF. You could specify the entire syntax of a language mathematically.
Meaning the language itself became a formal object you could reason about.
And that had enormous downstream effects. Compiler construction became an engineering discipline rather than an art. You could prove things about your language. You could write a parser generator that took a grammar and produced a parser. The language was no longer just a tool for talking to the machine — it was a system with its own internal logic and properties.
I want to go back to something. You mentioned FORTRAN's compiler producing code that rivaled hand-written assembly. But before that happened, there was an intermediate step that showed why compilation won the early race.
Speedcoding. Nineteen fifty-four. John Backus again, actually — before FORTRAN. Speedcoding was an interpreted system for the IBM seven-oh-one. You wrote in a higher-level notation, and the system interpreted it at runtime. The problem was performance — it ran ten to twenty times slower than hand-written machine code.
Ten to twenty times.
It was unusable for production work. And that experience taught Backus a lesson he carried into FORTRAN: if you want abstraction to be adopted, it cannot come with an unacceptable performance penalty. Interpretation was too slow. Compilation — translating to machine code ahead of time — was the only path that could deliver both abstraction and performance.
So the early race was won by compilation because the hardware couldn't absorb the interpretation overhead.
The hardware was too slow. A ten-to-twenty-x penalty on a machine that was already slow by modern standards was a non-starter. Compilation won not because it was philosophically superior but because it was practically necessary. The relationship to compute had to remain efficient even as it became more abstract.
That's a constraint that shaped everything that followed.
And it's a constraint that eventually got relaxed as hardware got faster, which opened the door to entirely different kinds of languages. But we're getting ahead of ourselves. Let me land the first part of this story. The origin era — plugboards to assembly to FORTRAN and its peers — was about establishing that an intermediary could be trusted. The compiler proved that a program could make decisions about hardware that were as good as or better than the programmer's. And that proof unlocked everything else.
Because once you trust the intermediary, you can give it more to do.
And that's exactly what happened next. The relationship didn't just get more distant — it got qualitatively different.
So the first leap was translation. But the real story is what happened when languages stopped translating and started abstracting.
C, nineteen seventy-two. Dennis Ritchie at Bell Labs. C is often called portable assembly, and that's exactly right. It still maps closely to hardware — pointers, manual memory management, no garbage collection. You can do things in C that map directly to what the processor is doing. But the instruction set is abstracted away. The same C source can compile for completely different processors.
So the relationship to compute became portable.
That was the structural shift. Before C, portability meant rewriting your assembly for every new machine. With C, you wrote once and compiled for different targets. The language didn't care which specific processor you were running on — the compiler handled that. The programmer's mental model was still close to the machine, but it was a generic machine, not a specific one.
And that generic machine was an abstraction that didn't exist before.
It's the first virtual machine, in a sense — not implemented in software, but in the programmer's head. You think in terms of memory addresses and pointers, but you don't know which physical addresses or which register file. The compiler maps your generic model to the specific hardware.
Then Smalltalk takes that idea and makes it real.
Smalltalk, developed at Xerox PARC through the seventies, pioneered the actual virtual machine concept. The language didn't compile to machine code at all — it compiled to bytecode for an abstract machine, and that abstract machine was implemented in software on the real hardware. The language now related to compute through a layer that didn't physically exist.
Which is a profound change. The language isn't talking to the machine anymore. It's talking to a machine that someone made up.
And that made-up machine could be implemented anywhere. Java took this mainstream in nineteen ninety-five — the JVM, write once run anywhere. The promise was that you could compile your Java source to bytecode, and that bytecode would run on any platform that had a JVM implementation. The language had stopped caring about the specific machine entirely.
But the performance question came back.
Early Java was slow. The JVM interpreted bytecode, and interpretation still carried a penalty. But this time the hardware was fast enough that for many applications, the penalty was acceptable. And then JIT compilation changed the game.
Just-in-time.
The idea is that the VM doesn't just interpret bytecode — it watches which code paths are hot, compiles them to native machine code at runtime, and keeps optimizing as it learns more about how the program actually runs. The language is now in a feedback loop with the hardware. It observes real execution patterns and adapts.
So the intermediary isn't just making decisions ahead of time anymore. It's making decisions continuously, based on actual runtime data.
And this is where the story gets almost... ironic. Modern JIT compilers — V8's TurboFan, the JVM's HotSpot — they do things that no ahead-of-time compiler can do. They can inline through virtual method calls because they know the actual types at runtime. They can unroll loops based on the actual iteration count they've observed. They can de-optimize and re-optimize if the program's behavior changes.
The intermediary has surpassed the human.
In many cases, yes. The abstraction tax has largely been eliminated by the very complexity the abstraction enabled. A modern JIT can produce machine code that's better than what a skilled assembly programmer would write, because it has information the programmer never had — actual runtime behavior on actual data.
Which is the full circle from the FORTRAN skepticism. We went from can a compiler possibly match hand-written assembly to the compiler routinely beats hand-written assembly.
And the compiler is doing it by watching the program run and adapting in real time. The relationship to compute is no longer static — it's dynamic, adaptive, bidirectional. The language learns how to relate to the specific hardware it's running on, with the specific workload it's handling, at this specific moment.
There's another thread here we haven't pulled. Memory.
Garbage collection.
Yes. When did the language start managing the hardware resource on the programmer's behalf?
Lisp, nineteen fifty-eight. John McCarthy. Automatic garbage collection was part of Lisp from the beginning. The programmer allocated memory and the runtime figured out when to free it. This was radical — the language was now managing a hardware resource that the programmer previously had to manage manually.
And the programmer's mental model is no longer the machine's model.
That's the deeper point. In C, you think about memory the way the machine thinks about memory — here's an address, here's what's stored there, I need to free this when I'm done. With garbage collection, you think about objects and references. The machine's model of memory — addresses, allocation, deallocation — is hidden from you. The language presents a different model entirely.
So it's not just that the language is doing the work for you. It's that the language is giving you a different way to think about the problem.
That's the generational shift in a nutshell. First generation, machine code — you think exactly like the machine. Second generation, assembly — you think like the machine but with mnemonics. Third generation, compiled high-level — you think in the language's abstractions, but you're still aware of the machine underneath. Fourth generation, declarative — you describe what you want, and the system figures out how. Fifth generation, logic programming — you state constraints and relationships, and the system searches for solutions.
Prolog.
Prolog is the canonical example. You don't write a procedure. You write facts and rules. The runtime does the search. The relationship to compute is almost unrecognizable from the plugboard days — you're not telling the machine what to do at all. You're describing a problem space and letting the machine explore it.
Which brings us to the framework question. The generations framework suggests a clean progression, but you and I both know it was never clean.
Never. FORTRAN IV was still running missile trajectory simulations in the late eighties. COBOL is still running banking systems today. The generations didn't replace each other — they piled up. Every layer of abstraction created new capabilities, but the old layers didn't disappear. They became infrastructure.
The relationship to compute isn't uniform even within a single modern application. You might have Python calling a C library running on a JVM that's JIT-compiling bytecode to native instructions.
The stack is real. A modern web request might touch JavaScript in the browser, which is JIT-compiled by V8, which calls a Java backend on the JVM, which calls a native library written in C, which executes machine code on the processor. That's four different language-compute relationships in a single request.
Four different answers to the question of who decides what the hardware does.
They all coexist. That's what the clean generational narrative misses. The history of programming languages isn't a staircase — it's a sedimentary deposit. Each layer compresses the ones below it but doesn't replace them.
Before we wrap, Hilbert's been making faces behind the desk. You've got something on this, don't you?
Hilbert: I wrote FORTRAN IV. Nineteen eighty-nine.
You did?
Hilbert: Defense contractor. Legacy codebase. Missile trajectory simulations originally written in the mid-sixties. They hired me as a junior programmer to maintain it.
What was that like?
Hilbert: Strange. The code felt like it was from another planet. All caps. Fixed-format columns. You had to put statements starting in column seven or the compiler would reject it. I was twenty-two years old writing code that was older than I was, running on hardware that was itself a decade out of date.
Why not rewrite it?
Hilbert: The physics models. Decades of validation against real test data. Nobody was going to sign off on a rewrite and risk introducing a discrepancy. So we maintained it.
What was the compiler like?
Hilbert: Ancient. Had a known bug where certain floating-point calculations would silently produce wrong results. Specific combinations of operations, specific value ranges. The engineers had built a correction table into the code — a lookup that said, if you're doing this calculation with values in this range, apply this offset.
Wait. They knew the compiler was producing wrong answers and they compensated in the application code?
Hilbert: For about fifteen years by the time I got there. It was documented. Page forty-three of the internal manual. Known compiler defect, workaround applied in subroutine TRAJ four.
The language's relationship to the hardware wasn't just abstracted — it was actively buggy, and the programmers' relationship to the language included knowing its failure modes better than its intended behavior.
Hilbert: That was the job. Knowing where the compiler lied to you.
That's... a perfect illustration of something we've been dancing around. The language-compute relationship is never purely theoretical. It's always mediated by specific implementations with specific bugs, running on specific hardware with specific quirks.
Hilbert: The correction table outlasted the compiler. They eventually got a new compiler that didn't have the bug, but the correction table stayed in the code for another three years because nobody wanted to be the person who removed it and broke the trajectory calculations.
The ghost of the bug outlived the bug.
Hilbert: I left in ninety-one. That code might still be running. I don't know.
Hilbert, that correction table detail is going to stick with me.
Hilbert: It stuck with the trajectories. That was the point.
Where does this end? If the trend is toward languages that increasingly manage their own relationship to hardware — JIT compilers that observe and adapt, runtimes that make decisions the programmer never sees — do we eventually reach a point where the programmer's mental model is entirely divorced from the machine?
I think we're already close. Most programmers today have no idea what the hardware is doing. They write Python or JavaScript, and layers of interpretation and compilation and optimization happen beneath them. The machine is there, doing the work, but the programmer's model of it is... fuzzy. And in many cases, that's fine. The abstractions hold.
But the machine hasn't gone away.
No. And this is the thing. The machine hasn't gone away — it's just gotten better at hiding. And when the abstractions break — when performance tanks for no obvious reason, when the garbage collector pauses at exactly the wrong moment — the programmer who understands the machine is the one who can fix it.
The question isn't whether abstraction is good or bad. It's whether we're training programmers who can reach down through the layers when they need to.
With AI-assisted code generation entering the picture, the next generation of languages might not even be designed by humans. What does that do to the relationship? If a language is optimized for machine generation rather than human readability, the entire contract changes. The language might relate to compute in ways that are efficient but incomprehensible to us.
The history of programming languages is really the history of humans deciding how much of the machine they want to think about. And each generation has answered less.
But the machine is still there. Still doing exactly what it's told — just through more and more intermediaries. The question is whether we still know what we're telling it.
Thanks to Hilbert Flumingtop for producing, and for the correction table story that I'm going to be thinking about for a while.
This has been My Weird Prompts. We'll be back soon.
Find us at my weird prompts dot com.