Every Programming Language Is a Theory of the Programmer
If machines only understand machine code, why do we keep inventing new languages to talk to them?
A computer understands exactly one language, yet people have invented thousands. Each one is a bet about who will be writing the program, what they should be spared, and which mistakes they must never be allowed to make.
For people who build products
Decisions worth studying
5 decisions from this history, each with the situation, the alternatives, what happened and a verdict. Open one for the full case. The verdict says how a decision turned out, which is not the same as whether it was wise; see moral luck.
-
Treat the compiler’s efficiency, not the language design, as the real challenge Find the single objection that stops adoption and make it disappear; elegance can wait.
- Situation
- Programming and debugging cost as much as three quarters of running a computer, but programmers doubted automatic translation could match hand-written code.
- Alternatives
- Focus on an elegant language and accept slower programs.
- Numbers
- An estimated six months stretched to more than two years; if FORTRAN code ran half as fast as hand code, the team believed adoption was in danger.
- What happened
- FORTRAN produced code good enough that working programmers adopted it, and launched high-level programming.
- Verdict
- The team identified the one objection that would block adoption and spent their effort removing it.
-
Add the null reference because it was easy to implement Language and platform shortcuts are permanent. Choose what is safe forever over what is easy now.
- Situation
- He wanted every reference checked by the compiler, but null was simple to build.
- Alternatives
- A type system with no null, or one where absence had to be handled explicitly.
- Numbers
- Hoare estimated the cost at perhaps a billion dollars over forty years.
- What happened
- Null references spread into most later languages and caused countless errors and crashes.
- Verdict
- Hoare himself called it a mistake. An easy choice under time pressure outlived its reasons by decades.
-
Trust the programmer with memory and leave out automatic checks Trusting the expert scales with the number of experts, not the size of the code.
- Situation
- Unix needed a language small and fast enough to replace assembly on small machines.
- Alternatives
- A safer, heavier language with checks and garbage collection.
- Numbers
- About 70% of serious security bugs at Chromium and of vulnerabilities fixed by Microsoft were memory-safety problems.
- What happened
- C became the language of operating systems for fifty years, and its memory errors became the raw material of security attacks.
- Verdict
- Excellent for its time and machines. The theory that careful experts can manage memory safely did not survive very large code bases.
A defining strength becomes the constraintPermissive defaults are borrowed time
-
Ship a scripting language in ten days that had to “look like Java” Whatever ships where everyone already is will set the standard; design it as if it will last.
- Situation
- The browser needed a little language for amateurs, and management wanted it fast and Java-like.
- Alternatives
- Embed an existing language such as Scheme, which Eich had been recruited to build.
- Numbers
- First implementation prototyped in ten days in May 1995.
- What happened
- Confusing design decisions lasted decades, but JavaScript became one of the most used languages in the world because it shipped in every browser.
- Verdict
- A rushed design that won anyway, because distribution, not elegance, decided its fate.
-
Add optional types on top of JavaScript instead of replacing it Improve the standard from inside rather than asking people to leave it.
- Situation
- Large teams struggled to maintain JavaScript code bases.
- Alternatives
- A new language that compiled to the browser.
- What happened
- TypeScript became the most used language on GitHub in August 2025, helped by types that catch errors in AI-generated code.
- Verdict
- Keeping full compatibility with the language everyone already used made adoption gradual and safe.
A computer understands one language. Underneath every program, however it was written, the processor executes numbers: instructions encoded as patterns of bits, specific to that family of chips. Nothing else is ever run.
And yet people have invented thousands of programming languages. New ones still appear every year, and arguments about them are among the most heated in the profession. If the machine has never cared which language a program was written in, the existence of all these languages can only be explained by the people on the other side. Programming languages are tools for humans. Their purpose is to let a person express what they want in a form the person can manage, and then let a translator turn it into the numbers the machine actually runs.
Alan Perlis, the first recipient of the Turing Award, put it in one of his 1982 epigrams: “A language that doesn’t affect the way you think about programming, is not worth knowing.”1 This essay starts from a related observation. Every programming language contains, usually implicitly, a theory of who the programmer is. It assumes something about their training, their goals, the mistakes they are likely to make and the costs that matter most. It then decides which responsibilities to leave with that person and which to take away. Following those theories through seventy years explains not only why so many languages exist, but why they changed in the order they did.
Before languages: programming as clerical labour
The first electronic computers were programmed by people who thought of the machine directly. Early machines were set up with switches and cables. Once programs could be stored in memory, they were written as numerical codes, each standing for one machine instruction. A programmer had to know where every value lived in memory, which registers held what, and the exact address to jump to next. Insert one instruction in the middle and every address after it might have to be recalculated by hand.
The first relief came from reuse rather than from language. At Cambridge, Maurice Wilkes, David Wheeler and Stanley Gill published a book in 1951 describing a library of subroutines for the EDSAC computer, prewritten routines for common tasks that a program could call. In the United States, Grace Hopper’s team at Remington Rand built A-0 for the UNIVAC around 1951 and 1952, a system that let programmers name library routines and have them gathered and linked into a program automatically; it was called a “compiler” because it compiled routines from a library.2 Symbolic assemblers let programmers write short mnemonics such as ADD instead of numeric operation codes, and names instead of raw addresses.
These tools reduced drudgery without changing the basic relationship. The programmer still thought in the machine’s terms: registers, addresses, instructions, one at a time. The theory of the programmer in this period was simple. They were a skilled technician of a particular machine, and their time was assumed to be cheaper than the machine’s.
FORTRAN: the economics of translation
That last assumption is where the first real programming language began. John Backus, describing the origins of FORTRAN at the first History of Programming Languages conference in 1978, was precise about the money. In 1954, he wrote, “the cost of programmers associated with a computer center was usually at least as great as the cost of the computer itself.” In addition, “from one-quarter to one-half of the computer’s time was spent in debugging. Thus programming and debugging accounted for as much as three-quarters of the cost of operating a computer; and obviously, as computers got cheaper, this situation would get worse.”3
This, Backus said, was “one of the prime motivations” for proposing the FORTRAN project to his manager at IBM, Cuthbert Hurd, in late 1953.3 The language would let scientists and engineers write formulas in a notation close to algebra, and a program would translate them into machine code for the new IBM 704.
The real problem was the translator
The hardest part of the project, in Backus’s account, was not the language at all. The computing community of 1954 was deeply sceptical of what was then called “automatic programming.” Experience with slow systems had convinced programmers that efficient code could not be produced automatically. Backus adds a telling detail: some “visionaries” had promoted their systems as having “almost human abilities to understand the language and needs of the user,” while closer inspection often revealed “a complex, exception-ridden performer of clerical tasks which was both difficult to use and inefficient.”4
The IBM 704 made the challenge sharper. It performed floating-point arithmetic in hardware, ten times faster than before, which removed the slow subroutines that had previously hidden the inefficiency of automatically generated code. There was, as Backus put it, nowhere left for inefficiency to hide. The team concluded that FORTRAN would be widely used “only if we could demonstrate that it would produce programs almost as efficient as hand coded ones and do so on virtually every job.” If a FORTRAN program ran only half as fast as hand-written code, “acceptance of our system would be in serious danger.”5 So the team regarded the design of the translator, the compiler, “as the real challenge, not the simple task of designing the language.”6
FORTRAN was completed in April 1957, after an estimated six months had stretched to more than two years, and its compiler produced code good enough that working programmers adopted it.7 Backus later wrote that he still believed “our emphasis on object program efficiency rather than on language design was basically correct.”6
What FORTRAN believed
The theory of the programmer embedded in FORTRAN was specific. The programmer was a scientist or engineer who thought in formulas. Their time was valuable, but the computer’s time was still precious, so the language could not cost the machine much. Responsibility for the arrangement of registers, the addresses of variables and the sequence of instructions moved from the person to the compiler. Responsibility for the mathematical method stayed with the person.
FORTRAN also established a pattern that runs through everything that followed. A language is only as good as its translator. The ideas expressed in a language matter, but whether people adopt it depends on whether the translation is trustworthy and affordable. Backus drew a prediction from this experience: the next revolution in programming would come only when two requirements were met together, a new kind of language far more powerful than existing ones, and “a technique has been found for executing its programs at not much greater cost than that of today’s programs.”6
Three other theories, almost at once
Within a few years of FORTRAN, several languages appeared that assumed quite different programmers. They are worth taking together, because their differences show that the choice of language was never purely technical.
COBOL: the business clerk
Hopper’s later work at Remington Rand produced FLOW-MATIC, used on the UNIVAC by 1957, which expressed data processing in English-like commands with names the user defined for their own data.2 Its audience was not scientists but business data processing: payrolls, inventories, billing. FLOW-MATIC strongly influenced COBOL, designed in 1959 and 1960 by a committee drawn from computer manufacturers and users under the auspices of CODASYL, the same body that would later standardise network databases.
COBOL’s theory of the programmer was almost the reverse of FORTRAN’s. Its users dealt with records and files rather than equations. Its programs would need to be read, audited and maintained by people other than the authors, possibly for decades. And businesses owned machines from different manufacturers. So COBOL used English words, separated the description of data from the procedures that used it, and aimed to run on different machines with little change. The cost being minimised was not machine time, but the cost of a business depending on code that only its author could read.
LISP: the mathematician of symbols
At MIT, John McCarthy was interested in a different problem altogether: programs that manipulate symbols and reason, the field that was beginning to be called artificial intelligence. His 1960 paper, “Recursive Functions of Symbolic Expressions and Their Computation by Machine,” defined LISP around a few primitive operations on lists, with functions that call themselves recursively, and with programs represented in the same list structure as data.8
LISP made one decision of lasting consequence. Programs that build and discard symbolic structures constantly need new memory and constantly abandon old pieces. McCarthy’s paper described a scheme in which the system itself found and reclaimed memory that was no longer reachable: an early published description of what became known as garbage collection.8 For the first time, a language took away from the programmer the responsibility for freeing memory. The cost was machine time and some unpredictability. The theory was that for the kind of person writing LISP, thinking about memory management was a distraction from the actual problem.
ALGOL: the language as a published object
The third theory came from an international committee. ALGOL 60 was designed as a common language for publishing algorithms, so that a method described in a journal could be read by anyone and implemented on any machine. Its report described the syntax with a precise formal notation, Backus–Naur Form, which made the language itself an object that could be studied mathematically. ALGOL’s commercial use was modest, but its block structure, its scoping rules and the very idea that a language should have a rigorous definition shaped most of the languages that came after.
Taken together, these languages show that by 1960 there was no single answer to “what should a language do for the programmer?” There were several, because there were several kinds of programmer, and several kinds of cost.
The crisis and the turn toward discipline
By the late 1960s, software had grown faster than the methods for building it. Large projects ran late, over budget and full of errors. A NATO conference on software engineering in 1968 gave the situation a name, the “software crisis,” and with it a new question. The early languages had asked how to make programs easier to write. The new question was how to make them possible to understand and trust.
The same year, Edsger Dijkstra published a short letter in Communications of the ACM arguing that the unrestricted go to statement made programs needlessly hard to reason about, because a program could jump anywhere, and the reader had to trace every path. The letter became the rallying point for “structured programming”: build programs from a small set of well-understood constructs, sequences, choices and loops, so that the structure of the text matches the structure of the computation.
This was a new kind of theory of the programmer. Earlier languages had assumed a competent person who needed tools. Structured programming assumed a fallible person who needed limits. It was the first time that removing a feature, rather than adding one, was put forward as progress.
An honest example of the opposite instinct comes from Tony Hoare. In 1965, designing a type system for references in ALGOL W, his goal was that every use of a reference would be checked by the compiler and be absolutely safe. But, he said in a talk in 2009, “I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement,” and he estimated the cost of the resulting errors and crashes over the following forty years at perhaps a billion dollars.9 The anecdote is valuable because it shows how language decisions are actually made: under time pressure, by thoughtful people choosing what is easy to build now over what is safe forever. The consequences of such choices outlive their reasons by decades.
C: trusting the expert who knows the machine
The language that would dominate systems programming for the next fifty years came, by contrast, from people who wanted fewer restrictions, not more.
Dennis Ritchie’s history of C is unusually candid about its origins. Ken Thompson began writing the Unix operating system in 1968 on a DEC PDP-7, “a machine with 8K 18-bit words of memory and no software useful to him,” and wrote it in assembly language despite wanting a higher-level language.10 Ritchie turned Thompson’s small language B, derived from BCPL, into C between 1971 and 1973, adding types so that it could describe the byte-addressed PDP-11. By early 1973 “the essentials of modern C were complete,” strong enough to rewrite the Unix kernel in C during that summer.11 Ritchie notes that portability was not originally a priority: “At the time we did not put much weight on portability; interest in this arose later.”10
C’s theory of the programmer is visible in what it chose not to check. Ritchie wrote that early compilers “did not complain about usages such as assigning between integers and pointers,” that supplying arguments of the right type to a function “was the responsibility of the programmer,” and that storage allocated at run time was left to a library routine, so that “the burden of managing it is placed on the programmer: C is hostile to automatic garbage collection.”12 Its strings were simply arrays of characters ending in a marker, which kept the language small but meant that “the burden of storage management for strings falls more heavily on the user.”13
Ritchie explained C’s success the same way. Its “types and operations are well-grounded in those provided by real machines,” and “for people used to how computers work, learning the idioms for generating time- and space-efficient programs is not difficult.” At the same time it was “sufficiently abstracted from machine details that program portability can be achieved.” It was “a tool to build larger tools,” and it took “a parsimonious, pragmatic approach.” His summary is one of the most quoted sentences in the field: “C is quirky, flawed, and an enormous success.”14
The theory here is the expert who understands the machine and should not be second-guessed. For writing operating systems on small computers in the 1970s, it was an excellent theory. It produced a language small enough to implement anywhere, fast enough to replace assembly, and portable enough that Unix, and later almost every operating system, could be written in it. The cost was invisible at first and enormous later, because a language that trusts the programmer with memory also trusts every bug.
Essence and accident
By the mid-1980s, many people hoped that the next language, the next method or the next tool would bring the dramatic gains in productivity that hardware enjoyed every year. In 1986 Fred Brooks, who had managed IBM’s OS/360 project, published an essay arguing that no such single breakthrough was coming. Its title was “No Silver Bullet,” and its argument rested, explicitly, on Aristotle.
Brooks divided the difficulties of software, “following Aristotle,” into essence, “the difficulties inherent in the nature of the software,” and accidents, “those difficulties that today attend its production but that are not inherent.” The essence of software, he argued, is “a construct of interlocking concepts: data sets, relationships among data items, algorithms, and invocations of functions.” The hard part of building software is “the specification, design, and testing of this conceptual construct, not the labor of representing it.”15
High-level languages, in this framing, had been the greatest attack on accident. “Surely the most powerful stroke for software productivity, reliability, and simplicity has been the progressive use of high-level languages for programming,” Brooks wrote, crediting it with at least a fivefold gain. A high-level language “frees a program from much of its accidental complexity”: the concrete machine program deals with “bits, registers, conditions, branches, channels, disks,” while the abstract program deals with operations, data types and sequences. But he set a limit. “The most a high-level language can do is to furnish all the constructs the programmer imagines in the abstract program,” and beyond some point “the elaboration of a high-level language becomes a burden that increases, not reduces, the intellectual task of the user.”16
Brooks’s distinction offers a way to read the whole history. Each language draws a line between what it treats as essential, which the programmer must still think about, and what it treats as accidental, which the language handles. FORTRAN declared registers accidental. LISP declared memory reclamation accidental. Structured programming declared arbitrary jumps harmful. C declared almost nothing accidental beyond the instruction set. Each line reflected what was affordable and what kind of mistake seemed most costly at the time. When costs shifted, the line moved.
The network changes the programmer
The 1990s brought two changes at once. Personal computers and then the internet put software on machines of every kind, owned by people who would never meet the programmer. And the number of programmers grew enormously, which meant that a language could no longer assume an elite.
Java: the ordinary developer on a large team
Java began as part of a research project at Sun Microsystems for software on networked consumer devices. Its 1995 white paper, by James Gosling and Henry McGilton, explains that “when the project started, C++ was the language of choice. But over time the difficulties encountered with C++ grew to the point where the problems could best be addressed by creating an entirely new language environment.”17
The paper is explicit about its theory of the programmer. The system was designed to be “simple, so it can be easily programmed by most developers; familiar, so that current developers can easily learn Java.” It should be learnable “without extensive programmer training.” Java was kept “looking like C++ as far as possible,” to let programmers “migrate easily,” while “removing the unnecessary complexities of C++.”18 And for reliability, a memory model with “no pointers or pointer arithmetic” was intended to eliminate “entire classes of programming errors that bedevil C and C++ programmers.”19 Programs compiled to “bytecodes,” an architecture-neutral format that a virtual machine could run on any platform. The authors noted, with some self-awareness, that their requirements “comprise quite a collection of buzzwords.”18
Compare this to C. Where C trusted the expert with memory, Java took memory away from everyone and collected the garbage automatically, as LISP had done thirty-five years earlier. Where C assumed the programmer knew the machine, Java assumed the program would run on machines nobody could predict. The cost moved from the programmer’s attention to the machine’s resources, a trade that had become affordable because hardware had become cheap, exactly as Backus had predicted in reverse.
JavaScript: a little language that became a platform
JavaScript’s origin is the clearest example of a language shaped by circumstances rather than design ideals, and its creators have documented it carefully. Brendan Eich joined Netscape in April 1995, recruited with the promise that he could “do Scheme in the browser.”20 But Netscape had just agreed to license Java, and senior managers at Netscape and Sun decided the browser needed a “little language” to complement it. Existing candidates, including Scheme, Perl, Python, Tcl and Visual Basic, were ruled out “due to business interests and/or time to market considerations.”20
The intended programmer was not Java’s professional. Following the model of Visual Basic alongside Visual C++, the scripting language was for “amateurs, part-time programmers, designers, accountants, and others,” people who would glue together components built by professionals.21 Marc Andreessen “stressed that Mocha should be so easy to use that anyone could write a few lines directly within an HTML document,” and management “reiterated the requirement that Mocha ‘look like Java,’ explicitly ruling out anything like BASIC.” Classes were left out “because supporting classes would take too long and risk competing with Java.” Eich prototyped the first implementation “in ten contiguous days in May, 1995.”21
Almost every feature of JavaScript that later caused confusion can be traced to these constraints: a syntax borrowed from Java over semantics closer to Scheme and Self, and decisions made in days to meet a demonstration deadline. Yet it became one of the most used languages in the world, for a reason that has nothing to do with its design. It shipped in the browser, and the browser was everywhere. Distribution, not elegance, decided its fate.
Python: the programmer’s time comes first
Python’s origin is gentler. In December 1989 Guido van Rossum, working at a research lab in Amsterdam, was “looking for a ‘hobby’ programming project that would keep me occupied during the week around Christmas.” He decided to write an interpreter for a scripting language he had been thinking about: “a descendant of ABC that would appeal to Unix/C hackers.”22 ABC was a teaching language he had helped build, elegant and aimed at non-professional programmers, but it never caught on. His explanation is instructive: “the difficulty of adding new ‘primitive’ operations to ABC. It was a monolithic, ‘closed system’ … I decided not repeat this mistake in Python.”22
Python’s theory of the programmer, stated in its later culture more than in any founding document, is that human time and human reading matter most. It is interpreted and slower than compiled languages, a cost that would have been unthinkable in Backus’s world and was perfectly acceptable in a world of cheap processors. It made readability a central value. And because it was extensible from the start, it could wrap fast libraries written in C and Fortran. That combination, simple to read and able to call fast code, is why it eventually became central to scientific computing and machine learning, territory FORTRAN had once owned.
The bill for trusting the expert
For decades, the choice between C’s control and Java’s safety looked like a stable division of labour: C and C++ for operating systems, browsers and anything that needed speed; managed languages for applications. What changed the calculation was security.
Software written in C and C++ had become the foundation of almost everything connected to the internet, and the memory mistakes that C left to the programmer had become the raw material of attacks. The Chromium project reported that around 70 percent of the high-severity security bugs it analysed were memory safety problems.23 Microsoft engineers reported in 2019 that roughly 70 percent of the security vulnerabilities Microsoft had fixed over twelve years were memory safety issues.24 These figures came from organisations with some of the most capable engineers in the world. The conclusion was uncomfortable: the theory that a sufficiently careful expert can manage memory safely did not survive contact with very large code bases.
Rust, which began as a personal project of Graydon Hoare and was later sponsored by Mozilla, offered a different answer. Its 1.0 announcement in May 2015 described it as “a new programming language aiming to make it easier to build reliable, efficient systems,” combining “low-level control over performance with high-level convenience and safety guarantees,” and achieving this “without requiring a garbage collector or runtime.”25 The mechanism is a set of rules about ownership and borrowing that the compiler checks: every piece of memory has one owner, and references to it are tracked so that the compiler can prove, before the program runs, that it will not be used after it is freed.
Rust’s theory of the programmer is the most demanding yet: an expert who wants C’s control, and who will still make mistakes, and who is therefore willing to argue with a compiler until it can prove those mistakes absent. It resolves the old dichotomy not by choosing between control and safety but by moving the safety check from run time, where Java put it, to compile time.
The argument moved from engineering into policy. In February 2024 the White House Office of the National Cyber Director published a report titled “Back to the Building Blocks: A Path Toward Secure and Measurable Software,” calling on the technical community to reduce the attack surface of software, with the National Cyber Director saying that “we need to tackle the hard problem of moving to memory safe programming languages.”26 A design decision made for small machines in the 1970s had become a matter of national security fifty years later.
A related shift happened in the most widely used language of all. TypeScript, released by Microsoft in 2012, added optional static types on top of JavaScript, letting large teams catch errors before running their code. In August 2025, by GitHub’s count of monthly contributors, TypeScript became the most used language on the platform for the first time.27 The pattern is familiar from structured programming: as programs and teams grow, languages add constraints, and programmers accept them.
When the machine writes the code
The newest chapter changes who is doing the writing. Code-completion tools based on large language models arrived in 2021, and after ChatGPT appeared in late 2022, models that could generate whole functions from a plain description became widely available. In January 2023 Andrej Karpathy, a well-known AI researcher, posted that “the hottest new programming language is English.”28 By 2025, Stack Overflow’s developer survey found that 84 percent of respondents were using or planning to use AI tools in their development process, and about half of professional developers used them daily.29
This is the most dramatic transfer of responsibility in the history of programming languages, and it revives a debate that is older than most programmers realise. In 1978 Backus recalled the sceptics of automatic programming who had promised systems with “almost human abilities to understand the language and needs of the user.”4 And in an essay published in 1979, Dijkstra argued against “natural language programming.” He pointed out that mathematics had advanced only when it adopted formal symbolism, and that “the virtue of formal texts is that their manipulations, in order to be legitimate, need to satisfy only a few simple rules; they are, when you come to think of it, an amazingly effective tool for ruling out all sorts of nonsense that, when we use our native tongues, are almost impossible to avoid.” His closing line was characteristically blunt: “I suspect that machines to be programmed in our native tongues … are as damned difficult to make as they would be to use.”30
The machines turned out to be easier to make than Dijkstra expected. What his argument anticipated correctly is where the difficulty would move. Developers in the same 2025 survey reported more distrust than trust in the accuracy of AI tools: 46 percent distrusted it, against 33 percent who trusted it.29 And the language that grew fastest during the same period was a typed one. GitHub’s own report explains the shift by saying that developers are moving “toward typed languages that make agent-assisted coding more reliable in production,” because “type systems reduce ambiguity and catch LLM errors before production.”27
In other words, natural language has become a way to produce code, but formal language remains the way to check it. The programming language has not disappeared. It has become the layer at which a machine’s guess about a person’s intention is made precise enough to be verified.
Understanding the philosophy
Programming languages look like technical artefacts, but the debates around them keep returning to questions philosophers have argued about for centuries: whether language shapes thought, whether reasoning can be made mechanical, what a thing essentially is, how much freedom people should have to harm themselves and others, how we can know that something is correct, and what happens to a craft when machines take it over. This section takes those questions one at a time. For each, it explains the idea plainly, introduces the tradition it belongs to, shows where it appears in the history of languages, sets out the strongest objection, and draws out what it means now. Where the language designers did not cite the philosophers involved, the connection is offered as an interpretation.
1. Notation as a tool of thought
The idea in plain terms
A language does not only express a solution; it changes which solutions you are likely to think of. A programmer fluent in LISP reaches for recursion and lists. A programmer fluent in SQL describes the result they want and lets the system find it. A programmer fluent in C thinks about memory addresses. Each language makes some ideas easy to state and others awkward, and over time what is easy to state becomes what is natural to think.
The tradition
In linguistics this is associated with the linguistic relativity hypothesis, often called the Sapir–Whorf hypothesis, after Edward Sapir and Benjamin Lee Whorf, who argued in the 1930s and 1940s that the structure of a language influences how its speakers perceive and think. Linguists distinguish a strong version (language determines thought, which is now widely rejected) from a weak version (language influences habitual thought, which has some experimental support). In computing, the clearest statement came from Kenneth Iverson, the creator of the language APL, whose 1979 Turing Award lecture was titled “Notation as a Tool of Thought.”31 Iverson argued, drawing on the history of mathematics, that good notation does more than record ideas: it suggests new ones. Alan Perlis’s epigram, quoted at the start of this essay, makes the same claim more briefly.
Where it shows up
FORTRAN let scientists write formulas that looked like algebra, and so scientific programs came to be organised around formulas. LISP’s single structure for code and data made programs that write programs feel natural, which is why it became the home of early artificial intelligence research. SQL’s declarative style trained a generation to describe what rather than how. React later brought the same declarative habit to user interfaces.
The strongest objection
Almost every general-purpose language is Turing complete: in principle, any computation expressible in one can be expressed in any other. On this view, languages differ only in convenience, not in what can be thought. And the strong form of linguistic relativity is not supported by evidence. The reply is that convenience is not trivial. What takes three lines in one language and three hundred in another will, in practice, be attempted far more often in the first.
What it means today
Choosing a language for a team is choosing the habits of mind the team will develop. And the rise of AI coding tools raises a new version of the question: if programmers increasingly describe problems in English and read code written by machines, which language will be shaping their thinking?
2. The dream of a language in which reasoning is calculation
The idea in plain terms
If arguments could be written in a perfectly precise language, then disputes could be settled by calculation, as arithmetic disputes are. A programming language is a partial realisation of this dream: a language precise enough that a machine can carry out what it says without interpretation.
The tradition
In the seventeenth century Gottfried Wilhelm Leibniz imagined a characteristica universalis, a universal symbolic language for all thought, together with a calculus ratiocinator, a method of reasoning within it. When people disagreed, he wrote, they would not need to argue; they could sit down and say, “Let us calculate.” Leibniz’s dream passed through George Boole’s algebra of logic, Gottlob Frege’s formal logic and the logicians of the early twentieth century directly into the theory of computation. Dijkstra’s essay against natural language programming invokes exactly this lineage, crediting “Vieta, Descartes, Leibniz, and (later) Boole” with the formal symbolisms that let “school children … learn to do what in earlier days only genius could achieve.”30
Where it shows up
Every programming language is a small, working characteristica: a domain where meaning is fixed precisely enough for a machine to act on it. ALGOL’s formal grammar, Backus–Naur Form, made the language itself a mathematical object. Type systems turned certain kinds of reasoning about programs into mechanical checks.
The strongest objection
Formal precision has limits. Programs implement specifications, and specifications are written, at least at first, in natural language, by people who are often unsure what they want. Fred Brooks argued that the hardest part of software is precisely deciding what to build, a task no formal language can automate. And language models now show that natural language can be turned into working code, reversing Leibniz’s direction: rather than forcing thought into a precise language, machines translate imprecise language into precise code.
What it means today
The two directions now coexist. People increasingly state intentions loosely and let machines formalise them; formal languages remain the place where those formalisations are checked. Leibniz’s “let us calculate” has become, in practice, “let us check.”
3. Essence and accident
The idea in plain terms
Some difficulties in programming come from the problem itself: understanding what a hospital scheduling system must do is hard no matter what tools you use. Other difficulties come from the tools: managing registers, freeing memory, matching brackets. The first kind cannot be removed; the second kind can.
The tradition
This distinction is Aristotle’s. In his metaphysics, the essence of a thing is what makes it the kind of thing it is, the properties it could not lack without ceasing to be itself. An accident is a property a thing happens to have but could lose: Socrates is essentially human but only accidentally pale or seated. Brooks applied the distinction to software in 1986, explicitly “following Aristotle”: the essence of software is “a construct of interlocking concepts,” and its accidents are “those difficulties that today attend its production but that are not inherent.”15 High-level languages, he argued, had been the most powerful attack on accident ever made, and no future tool could remove the essence.
Where it shows up
Each major language moved the line. FORTRAN made register allocation accidental. LISP and later Java made memory reclamation accidental. Rust made memory safety a property the compiler checks rather than one the programmer must remember. Each time, something that programmers had treated as part of the job turned out to be removable.
The strongest objection
The line between essence and accident is not fixed, and that is philosophically interesting. Memory management was treated as accidental when Java removed it, yet for systems programming it later turned out to be bound up with an essential concern: security. What counts as essential depends on what the program is for and on the conditions it runs in. Aristotle’s categories were meant to be stable; in software, they shift with the world.
What it means today
AI coding tools are the largest attack on accident yet: they can produce boilerplate, translate between languages and write routine functions. If Brooks was right, they will not remove the essence, the work of deciding precisely what a program should do. They may, however, make that essential work more visible, because it will be most of what remains.
4. Freedom and protection
The idea in plain terms
C lets programmers do almost anything, including things that crash programs and open security holes. Java and Rust forbid some of those things. Every language takes a position on how much freedom programmers should have, and how much the language should protect them, and others, from mistakes.
The tradition
This is one of the oldest questions in political philosophy. In On Liberty (1859), John Stuart Mill argued that the only legitimate reason to restrict a person’s freedom against their will is to prevent harm to others; a person’s own good is not sufficient warrant. The opposite view, paternalism, holds that it can be right to restrict people for their own good. In 2008 Richard Thaler and Cass Sunstein proposed a middle path they called libertarian paternalism: design choices so that the default is the safe or beneficial option, while leaving people free to choose otherwise.32
Where it shows up
C embodies a strongly libertarian design: the programmer is trusted completely. Java is closer to paternalism: pointers are simply not available. Rust is a near-perfect example of libertarian paternalism. Its safe subset is the default, and the compiler enforces it; but a programmer can mark a block of code unsafe and take full responsibility for it. TypeScript’s optional types follow a similar logic, adding protection without removing the escape hatch.
The strongest objection, and Mill’s answer
Defenders of C’s freedom could invoke Mill: experts should be free to take risks with their own programs. But Mill’s principle cuts the other way here. Memory errors in widely used software do not mainly harm the programmer; they harm the millions of people whose computers run the code. The Chromium and Microsoft figures on memory-safety vulnerabilities describe harm to others, which is exactly where Mill allows restriction. The 2024 White House report’s call for memory-safe languages can be read as a public application of the harm principle to software.
What it means today
Choosing a language for software others will depend on is a moral choice about risk, not only a technical preference. The libertarian-paternalist pattern, safe by default and unsafe by explicit choice, has become the industry’s preferred settlement.
5. How do we know a program is right?
The idea in plain terms
You can test a program by running it on examples. Or you can try to prove that it is correct for every possible input. These are two different ways of knowing, and the history of programming languages is partly a contest between them.
The tradition
Philosophers distinguish knowledge from experience, gained by observation (empirical knowledge), from knowledge by reasoning, as in mathematics (a priori knowledge). Testing is empirical; proof is a priori. Dijkstra put the limitation of testing in one sentence in 1970: “Program testing can be used to show the presence of bugs, but never to show their absence!”33 It is, in effect, the problem of induction that David Hume described in the eighteenth century: no number of observed cases guarantees the next. In 1969 Tony Hoare published “An Axiomatic Basis for Computer Programming,” which treated programs as mathematical objects about which theorems could be proved.34 A deeper connection, known as the Curry–Howard correspondence, shows that type systems and logic are two views of the same structure: a type is like a proposition, and a program of that type is like a proof of it.
Where it shows up
Every type checker performs a small, automatic proof. When Rust’s compiler accepts a program, it has proved that the program does not have certain kinds of memory error. GitHub’s explanation of TypeScript’s rise, that “type systems reduce ambiguity and catch LLM errors before production,” is a statement that proof-like checking is becoming more valuable as more code is written by machines.27
The strongest objection
Full proofs are expensive and rare, and a proof only shows that a program matches its specification; if the specification is wrong, the proof is worthless. Most software is still known to be correct, insofar as it is, mainly through testing and use. The practical answer has been a mixture: lightweight proofs through types for the properties that matter most, and testing for the rest.
What it means today
When a machine writes code that no human fully read, the question of how we know it is right becomes urgent. The likely result is that the a priori side, types, formal checks, verified components, gains ground, because empirical confidence based on human review becomes harder to maintain.
6. Meaning is use: why languages spread
The idea in plain terms
A language’s design does not decide its success. C spread because Unix spread; JavaScript spread because every browser ran it; Python spread because scientists and then machine learning researchers adopted it. A language lives through the community that uses it.
The tradition
In his later work, Philosophical Investigations (1953), Wittgenstein argued against the idea that words have meaning by standing for things. “For a large class of cases,” he wrote, “the meaning of a word is its use in the language.”35 Language is woven into what he called “forms of life,” shared practices that give words their sense. A word with no community of users has no meaning in this sense at all.
Where it shows up
The history is full of elegant languages that remained small and awkward ones that became universal. JavaScript’s creators documented how a language designed in ten days for part-time scripters became the language of the web because of where it was deployed. Ritchie attributed C’s success first to Unix. COBOL persists in banks because of the enormous body of code and practice built around it. In each case, the language’s meaning, what it is for and how it is written well, was made by its users.
The strongest objection
Design still matters. Rust grew in part because its design solved a real problem that other languages had not. A good design can attract a community; a bad one can repel it. The fairer conclusion is that design determines what a language makes possible, while use determines what it becomes.
What it means today
Languages now acquire a new kind of user: the language model, trained on existing code. Popular languages, with more code to learn from, may be served better by AI tools, which could reinforce their popularity. Use shapes meaning, and now machines are among the users.
7. The programmer’s craft
The idea in plain terms
Programming has always been both an intellectual art and a kind of labour. Each new language changed what the labour involved, and AI tools are changing it again.
The tradition
Brooks captured the art: “The programmer, like the poet, works only slightly removed from pure thought-stuff. He builds his castles in the air, from air, creating by exertion of the imagination.”36 The philosopher Gilbert Ryle distinguished knowing that (facts one can state) from knowing how (skills one can exercise), and Michael Polanyi observed that “we can know more than we can tell,” describing the tacit knowledge that experts cannot fully articulate. On the labour side, the sociologist Harry Braverman argued in 1974 that industrial management tends to deskill work by separating its conception from its execution.37
Where it shows up
High-level languages could be described as deskilling assembly programming, yet they vastly increased the number of programmers and the ambition of what they built. AI tools now separate conception (describing what is wanted) from execution (writing the code) more sharply than ever before. The skills that remain essential, judging whether code is right, designing systems, understanding a domain, are largely tacit, the kind of knowing how that Ryle and Polanyi described.
The strongest objection
Every earlier abstraction was greeted by fears of deskilling, and each created more skilled work, not less. It may be the same this time. The difference worth watching is that tacit knowledge is usually acquired by doing the very work now being automated. If novices no longer write routine code, it is an open question how they will develop the judgement needed to review it.
What it means today
The craft is shifting from writing to judging. The programmer’s most valuable knowledge may increasingly be knowing how to tell whether something a machine produced is right, a skill that has always been built through practice.
Bringing the threads together
Read philosophically, the history of programming languages is a long negotiation between human thought and mechanical precision. Languages shape how programmers think, and they are attempts at Leibniz’s dream of a language in which reasoning becomes calculation. They draw Aristotle’s line between essence and accident, and redraw it as conditions change. They take positions on Mill’s question of freedom and harm. They embody different answers to how we can know a program is right. They succeed through use as much as design. And they keep redefining the programmer’s craft. AI is the newest turn in that negotiation, not its end: as machines take over more of the writing, the philosophical questions about precision, proof and responsibility move to the centre of the work.
What cannot be handed over
Return to the question at the start. If machines only understand machine code, why do we keep inventing languages? The answer, I think, is that each generation discovers a new set of things the programmer should not have to think about, and builds a language that takes them away. In that sense every language is a theory of the programmer, and every new language is a revised theory.
Brooks’s distinction marks the limit of that process. Languages can remove accidental difficulty: registers, memory, types, perhaps now even much of the typing itself. They cannot remove the essential difficulty, which is deciding precisely what a program should do and knowing whether it does it. AI tools continue the long transfer of responsibilities from human to machine, and they may be the largest step in it. But the evidence so far points in the same direction as every earlier step. As the writing becomes easier, the checking matters more, and the formal language survives as the place where intention becomes exact.
Dijkstra was wrong that machines could not be made to accept our native tongues. He may still be right about why formal languages exist. They were never only a way of speaking to machines. They are the way people make themselves precise, and precision is the one responsibility that can be assisted but not delegated.
Selected bibliography
- Backus, John. “The History of FORTRAN I, II, and III.” In History of Programming Languages, ed. Richard L. Wexelblat, 25–74. Academic Press, 1981 (from the 1978 HOPL conference). PDF
- McCarthy, John. “Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I.” Communications of the ACM 3, no. 4 (1960): 184–195.
- Ritchie, Dennis M. “The Development of the C Language.” ACM SIGPLAN Notices 28, no. 3 (1993): 201–208 (HOPL-II). PDF
- Brooks, Frederick P. “No Silver Bullet: Essence and Accidents of Software Engineering.” 1986; reprinted in Computer (1987) and The Mythical Man-Month, anniversary edition (1995). PDF
- Dijkstra, Edsger W. “On the Foolishness of ‘Natural Language Programming’” (EWD667). In Program Construction, Lecture Notes in Computer Science 69. Springer, 1979. Transcription
- Perlis, Alan J. “Epigrams on Programming.” ACM SIGPLAN Notices 17, no. 9 (1982): 7–13.
- Gosling, James, and Henry McGilton. The Java Language Environment: A White Paper. Sun Microsystems, October 1995. PDF
- Wirfs-Brock, Allen, and Brendan Eich. “JavaScript: The First 20 Years.” Proceedings of the ACM on Programming Languages 4, HOPL (June 2020), Article 77. PDF
- van Rossum, Guido. Foreword to Mark Lutz, Programming Python, 1st ed. O’Reilly, 1996. python.org
- The Rust Core Team. “Announcing Rust 1.0.” 15 May 2015. blog.rust-lang.org
- Office of the National Cyber Director. Back to the Building Blocks: A Path Toward Secure and Measurable Software. The White House, February 2024.
- GitHub. Octoverse 2025. October 2025.
- Stack Overflow. 2025 Developer Survey.
- Iverson, Kenneth E. “Notation as a Tool of Thought.” Communications of the ACM 23, no. 8 (1980), Turing Award lecture of 1979.
- Mill, John Stuart. On Liberty. 1859.
- Thaler, Richard H., and Cass R. Sunstein. Nudge. Yale University Press, 2008.
- Hoare, C. A. R. “An Axiomatic Basis for Computer Programming.” Communications of the ACM 12, no. 10 (1969).
- Dijkstra, Edsger W. “Notes on Structured Programming” (EWD249). 1970.
- Wittgenstein, Ludwig. Philosophical Investigations. 1953.
- Brooks, Frederick P. The Mythical Man-Month. Addison-Wesley, 1975.
- Ryle, Gilbert. The Concept of Mind. Hutchinson, 1949.
- Polanyi, Michael. The Tacit Dimension. 1966.
- Braverman, Harry. Labor and Monopoly Capital. Monthly Review Press, 1974.
Research limitations
The history of early languages is told largely by their creators, decades after the fact, and the HOPL papers used here are participants’ accounts rather than neutral histories. Hopper’s motivations for English-language commands are widely described in secondary sources, but the primary material consulted here documents what FLOW-MATIC did rather than why, so the essay describes its design and audience rather than her intentions. Dijkstra’s 1968 letter and the 1968 NATO report are described from standard knowledge of their content rather than quoted. The memory-safety percentages come from specific organisations’ code bases and periods and should not be generalised as industry-wide rates. Survey figures describe self-selected respondents. The framing of each language as a “theory of the programmer,” and the reading of the history as a transfer of responsibilities driven by changing costs, are the author’s interpretation. The philosophical section connects programming languages to Aristotle, Leibniz, Mill, Hume, Wittgenstein, Ryle, Polanyi, Braverman and the Sapir–Whorf debate; apart from Brooks’s explicit use of Aristotle and Dijkstra’s references to Leibniz, these are the author’s interpretive connections, offered to make the ideas understandable rather than as claims about what language designers intended.
Footnotes
-
Alan J. Perlis, “Epigrams on Programming,” ACM SIGPLAN Notices 17, no. 9 (September 1982): 7–13, epigram 19. ↩
-
IEEE Milestones, “Grace Hopper’s Compiler and Programming Language Work, 1952–1959,” ieeemilestones.ethw.org. ↩ ↩2
-
John Backus, “The History of FORTRAN I, II, and III,” in History of Programming Languages (Academic Press, 1981), §1.2 “The Economics of Programming,” pp. 26–27. ↩ ↩2
-
Backus, “History of FORTRAN,” §1.3, p. 28. ↩
-
Backus, “History of FORTRAN,” p. 32. ↩
-
John McCarthy, “Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I,” Communications of the ACM 3, no. 4 (1960): 184–195. ↩ ↩2
-
Tony Hoare, “Null References: The Billion Dollar Mistake,” QCon London, 2009; abstract and recording at InfoQ. ↩
-
Dennis M. Ritchie, “The Development of the C Language” (HOPL-II, 1993), p. 2 of the author’s version. ↩ ↩2
-
Ritchie, “Development of C,” “Portability,” p. 9. ↩
-
Ritchie, “Development of C,” pp. 12–13. ↩
-
Ritchie, “Development of C,” p. 13. ↩
-
Ritchie, “Development of C,” “Whence Success?,” pp. 13–14. ↩
-
Frederick P. Brooks, “No Silver Bullet: Essence and Accidents of Software Engineering” (1986), p. 2 of the reprint cited in the bibliography. ↩ ↩2
-
Brooks, “No Silver Bullet,” “High-level languages,” p. 5. ↩
-
James Gosling and Henry McGilton, The Java Language Environment (Sun Microsystems, October 1995), §1.1, p. 12. ↩
-
Gosling and McGilton, §1.2.2 “Robust and Secure,” p. 14. ↩
-
Allen Wirfs-Brock and Brendan Eich, “JavaScript: The First 20 Years,” Proc. ACM Program. Lang. 4, HOPL (2020), Article 77, §2.1, p. 77:7. ↩ ↩2
-
Guido van Rossum, foreword to Programming Python (1st ed., 1996), python.org/doc/essays/foreword. ↩ ↩2
-
The Chromium Projects, “Memory safety,” chromium.org, based on an analysis of 912 high or critical severity security bugs since 2015. ↩
-
Matt Miller (Microsoft Security Response Center), presentation at BlueHat IL, February 2019, as widely reported; Microsoft has since repeated the figure in its own security writing. ↩
-
The Rust Core Team, “Announcing Rust 1.0,” 15 May 2015, blog.rust-lang.org. ↩
-
The White House, Office of the National Cyber Director, press release for Back to the Building Blocks: A Path Toward Secure and Measurable Software, 26 February 2024, archived at bidenwhitehouse.archives.gov. ↩
-
GitHub, “Octoverse: A new developer joins GitHub every second as AI leads TypeScript to #1,” 28 October 2025, github.blog. The ranking is by monthly contributors over September 2024 to August 2025. ↩ ↩2 ↩3
-
Andrej Karpathy, post on X, 24 January 2023; source traced by Quote Investigator, quoteinvestigator.com. ↩
-
Stack Overflow, 2025 Developer Survey, “AI,” survey.stackoverflow.co/2025/ai. ↩ ↩2
-
Edsger W. Dijkstra, “On the Foolishness of ‘Natural Language Programming’” (EWD667), published in Program Construction (Springer LNCS 69, 1979), transcription. ↩ ↩2
-
Kenneth E. Iverson, “Notation as a Tool of Thought,” 1979 ACM Turing Award lecture, Communications of the ACM 23, no. 8 (1980). ↩
-
Richard H. Thaler and Cass R. Sunstein, Nudge: Improving Decisions about Health, Wealth, and Happiness (Yale University Press, 2008). ↩
-
Edsger W. Dijkstra, “Notes on Structured Programming” (EWD249, 1970). ↩
-
C. A. R. Hoare, “An Axiomatic Basis for Computer Programming,” Communications of the ACM 12, no. 10 (1969). ↩
-
Ludwig Wittgenstein, Philosophical Investigations (1953), §43. ↩
-
Frederick P. Brooks, The Mythical Man-Month (1975), chapter 1, “The Tar Pit.” ↩
-
Harry Braverman, Labor and Monopoly Capital: The Degradation of Work in the Twentieth Century (Monthly Review Press, 1974). ↩
How it evolved
The arc
- 1951
Wilkes, Wheeler and Gill publish a library of subroutines for the EDSAC. Grace Hopper’s A-0 links library routines by name on the UNIVAC.
- 1957
FORTRAN ships for the IBM 704, built on the bet that a compiler could produce code almost as fast as a skilled human.
- 1960
McCarthy’s LISP paper appears, including an early description of garbage collection. ALGOL 60 is published; COBOL is specified for business data processing.
- 1965
Tony Hoare adds the null reference to ALGOL W because it was easy to implement, a choice he later called his “billion-dollar mistake.”
- 1968
The NATO conference names a “software crisis.” Dijkstra’s letter against the go to statement launches the argument for structured programming.
- 1973
Ritchie and Thompson rewrite the Unix kernel in C, a language whose types and operations are grounded in real machines.
- 1986
Fred Brooks, following Aristotle, separates software’s essential difficulties from its accidental ones and credits high-level languages with removing the latter.
- 1991
Python is first released, a descendant of the teaching language ABC built to be extensible.
- 1995
Java promises safety and portability for ordinary developers. JavaScript is prototyped in ten days to “look like Java.”
- 2015
Rust 1.0 offers memory safety without a garbage collector or runtime.
- 2024
The White House calls on the technical community to move toward memory-safe languages.
- 2025
TypeScript becomes the most-used language on GitHub by contributors, and 84 percent of surveyed developers use or plan to use AI tools.
Left open
Questions to sit with
- If each language encodes a theory of who the programmer is, what theory is encoded in a system where the programmer mostly reviews code written by a machine?
- Which of today’s “accidental” difficulties will look essential in hindsight, and which of today’s essentials will a future language quietly remove?
- Is a mistake that a language makes impossible still something the programmer understands, or only something they no longer need to think about?
Follow the thread
Where this essay connects
Shrikant Kumar. “Every Programming Language Is a Theory of the Programmer.” Essay on Programming Languages, Telos, published 16 July 2023, updated 22 October 2026. https://shrikantk.com/blog/every-programming-language-is-a-theory-of-the-programmer/