Christopher Strachey changed his mind about machine code sometime around 1965.
He was building the General Purpose Macrogenerator (the GPM) at Cambridge to help write a compiler for the Combined Programming Language (a C precursor). The original plan was simple: mix machine code with macro calls where convenient. The GPM was designed to make this possible.
He ended up abandoning the mix entirely. Instead, for the CPL compiler, all machine code would be incorporated as macro calls: even sections called only once, where defining a macro added no economy at all. It had started as a way to save effort, but it became a principle.
Sixty years later, programmers and writers are asking similar questions about LLMs that Strachey’s contemporaries were asking about macro systems: are the tools legitimate, and does the work count.
Wheeler’s subroutine#
The groundwork comes thirteen years earlier. In 1952, David Wheeler presented a short paper at the ACM national meeting: “The Use of Sub-Routines in Programmes.”1 Wheeler had been part of the EDSAC team at Cambridge, which built one of the first practical stored-program computers.
His paper introduces the library subroutine as a unit of abstraction. A subroutine is self-contained, reusable, testable in isolation. You call it; control returns to the point after the call. From the outside it behaves like a single instruction, even though it may be dozens. Wheeler’s summary: “All complexities should — if possible — be buried out of sight.”
The asymmetry problem#
By the early 1960s, macro-assemblers were standard. You define a macro; the assembler expands it into an instruction sequence before assembly. It extends the instruction set without modifying the hardware.
Strachey identified a structural problem with all of them. A conventional macro takes text as parameters (register names, addresses, literal values, arbitrary strings) and produces complete instructions as output. The domain (what a function accepts as input) and range (what it produces as output) don’t overlap. In Strachey’s terms: “the domain and range of these macro-functions do not overlap.”2 This limits composition. You cannot build complex behavior by nesting simpler behaviors, because the types don’t match.
GPM eliminates this by making everything a character stream. Input is a stream of characters. Output is a stream of characters. A macro call is a pattern in the stream; its expansion is substituted back into the stream. That re-entry is the key step: if the expansion contains another macro call, that call gets expanded too. The system processes its own results. Because domain and range are now the same type, a macro call can appear anywhere a string can appear: as a parameter to another macro call, in the name position of a macro call, inside a macro’s own definition. Recursion, conditionals, and higher-order behavior follow directly from the unification. Strachey notes they “appeared as it were in the wash.”
This is also why the all-macros discipline was a structural requirement, not a preference. Allow raw machine code anywhere in the stream alongside macro calls and you have introduced a second type: patterns to be expanded, and instructions to be left alone. GPM would need rules to tell them apart. The uniformity breaks down, and with it the recursive model. The decision Strachey arrived at (no machine code in the source, only macro calls, even where defining a macro added no economy) was not aesthetic. It was the condition the system required.
The CPL compiler was written entirely in macro calls; the programmer never touched opcodes. Strachey noted the consequence: “even very experienced programmers indeed tend to spend hours simulating its action when one of their macro definitions goes wrong.” Power and opacity arrived together.
The pattern#
A former student once put it to me that every abstraction layer gets rejected by the practitioners of the one below, in the same cultural register each time. She was naming a pattern the record shows plainly: each layer has produced the same debate. Is this real programming? Do you understand what the machine is actually doing?
Subroutines raised it, and so did macro systems. FORTRAN raised it loudly: John Backus’s team spent years arguing that compiled code could match hand-written machine code and therefore be trusted.3 High-level languages raised it in turn. Each time, the layer was legitimized, and the locus of required skill shifted upward. The machine kept receding and the question kept returning.
What is different about LLMs#
Questions about whether LLM-assisted code is real programming, or text written with LLM help is real writing, have revived the legitimacy debate. History does not guarantee its outcome, although the debate will resolve in practice as practitioners use the best available tools and learn their properties; market distortions aside, that approach is good engineering and usually wins out over time. The more interesting question is what kind of abstraction an LLM provides.
Every previous abstraction layer was deterministic and traceable. A macro expansion has a defined structure: given a name and the current state, you can compute the result by hand. A compiler’s transformation of source to object code is, in principle, auditable. The failure modes are structural (unmatched brackets, undefined names) and they are reported precisely.
LLMs are not like this. The binding between a prompt and its output is not a defined rule you can look up. It is distributed across a weight matrix, a large table of numerical values produced by training on text you did not write and cannot inspect. The output is not deterministic. The failure modes are not structural; they are probabilistic. The model can fail silently: the output looks plausible, reads as confident, and nothing flags it as wrong. There is no Find routine you can call to check the current state.
Using the layer well begins with an accurate account of these properties. Strachey’s design suggests a general principle he did not state directly: an abstraction layer derives its power from full commitment because partial adoption breaks the model.
Strachey was not alone in finding it. Dijkstra hit the same constraint with structured programming: full commitment to provable control structures was the condition for formal reasoning, and its popular reduction to “abolish goto” lost that property entirely.4 Kay hit it with OOP: the real innovation was message-passing, not objects, and most languages adopted the lesser idea.
Strachey’s commitment eventually became infrastructure we take for granted; for LLMs, the corresponding discipline begins with learning their failure modes and is now taking shape through verification practices, workflow conventions, and decisions about where human judgment belongs.
-
Wheeler, D.J. (1952). “The Use of Sub-Routines in Programmes.” Proceedings of the ACM national meeting, Pittsburgh, pp. 235–236. ↩︎
-
Strachey, C. (1965). “A General Purpose Macrogenerator.” The Computer Journal, 8(3), pp. 225–241. ↩︎
-
Backus, J. (1979). “The History of FORTRAN I, II, and III.” Annals of the History of Computing, 1(1), pp. 21–37. ↩︎
-
Dijkstra, E.W. (1968). “Go To Statement Considered Harmful.” Communications of the ACM, 11(3), pp. 147–148. See also Dijkstra, E.W. (1972). “Notes on Structured Programming.” In O.-J. Dahl, E.W. Dijkstra, and C.A.R. Hoare, Structured Programming, Academic Press, pp. 1–82. ↩︎