Write-only languages
Code no human can read, and code no compiler checks
Seeded 2010 Written October 2026
In 1972, an engineer at Signetics had had enough of the company's approval process. Every new component specification had to crawl through a long chain of sign-offs, "during which no actual checking seemed to occur", as the Jargon File puts it. So the engineer wrote a specification for a memory chip you could write to but never read from, slipped it into the pile with all the others, and waited to see who would notice.
Nobody did. The Signetics 25120, a "fully encoded, 9046 x N, Random Access, write-only-memory", made it into the data book, complete with a characteristic curve for "number of pins remaining vs. number of socket insertions". Management found out when regular customers started calling to ask for pricing.
That's the Jargon File's version, anyway. Other tellings say it was a joke from the start, and it certainly ended up as one: Signetics later ran it as an April Fools' gag. I prefer the version with a moral.
Programmers have never met an engineering joke they didn't want to steal, so the term moved over to software. Write-only code, according to the same Jargon File, is code "so arcane, complex, or ill-structured that it cannot be modified or even comprehended by anyone but its author, and possibly not even by him/her". A write-only language is one "with syntax (or semantics) sufficiently dense and bizarre that any routine of significant size is automatically write-only code". The Jargon File awarded the title to APL, occasionally to C, and noted that INTERCAL and TECO deserved it more. Ward Cunningham's original wiki has a page on it too, and anyone who has opened a file they wrote six months ago has met the concept in person.
Here is Conway's Game of Life, in one line of APL:
life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}
I'm told it's elegant. It certainly needed its own keyboard. Edsger Dijkstra called APL "a mistake, carried through to perfection", and as insults go, that one is hard to improve on.
And then there are regular expressions, the one write-only language every programmer speaks.
My list
Everyone has a list. Mine has Perl, bash, Scala and Rust on it, along with most of functional programming. That's my list for humans, mind you. AI has no such limitations, and we'll get to what that means.
Yes, Rust. Rust lovers, read this to the end and you'll be overjoyed. At least, you will be if you're not still convinced that you write better code than a strong modern AI model that's been told to put some effort into it.
Perl is the easy one. Keith Bostic called it "the only language that looks the same before and after RSA encryption". Its motto is "There's more than one way to do it", which is a fine motto for a jazz band and a terrible one for a code base. It has a special variable for nearly every punctuation mark on the keyboard. $_ is whatever you were just talking about. $; is the subscript separator, obviously. An array means its length or its first element, depending on whether you put parentheses around the variable you assign it to. Perl hackers even test whether a number is prime with a regular expression, /^1?$|^(11+?)\1+$/, run against the number written out in unary. It works, which is the worst thing I can say about it.
Larry Wall, who designed Perl, famously listed the three great virtues of a programmer as laziness, impatience and hubris. You can find all three in the language. Mostly the third.
Bash is the one nobody gets to avoid. You can refuse to touch Perl. Bash runs your build, your deploy and your containers, so sooner or later every normal person has to read some and write some. It's a minefield with a prompt. "$@" and $@ are different things, [ ] and [[ ]] are different things, and ${file%.*} strips a file extension, as everyone knows. My rule of thumb is that every line of bash, or every short paragraph of it, gets a comment above it, longer than the code, explaining in human terms what the code is actually doing. A comment longer than the code it explains is normally a bad sign. In bash, it's the minimum.
Scala set out to be a better Java and never learned to say no. It has objects, functional programming, implicit conversions, symbolic operators, a type system you can do arithmetic in, and XML literals in the grammar. In Scala 2, if you wanted a type lambda, you wrote ({type L[A] = Either[Int, A]})#L, and people did, in production, on purpose. Implicits let code run that appears nowhere on the page, chosen by an import you can't see from where you're standing. And one of its most popular libraries gave the world |@|, an operator its own community calls Admiral Ackbar, Cinnabon or Macaulay Culkin, because any of those is easier to remember than what it does.
It is possible to write good Scala. Write it mostly as you would Java. It runs on the JVM anyway. Nothing in the toolchain will enforce that for you, but a well-instructed AI reviewer could.
Rust is the one that's going to get me into trouble. Stack Overflow's survey has named it the most loved, and later the most admired, language every year for a decade, which tells you a great deal about the kind of people who fill out Stack Overflow surveys. Here's a perfectly ordinary type from a perfectly ordinary async code base:
Pin<Box<dyn Future<Output = Result<Vec<u8>, Box<dyn Error + Send + Sync>>> + Send + 'static>>
The syntax for telling a generic function which type you meant is baroque enough to have earned a nickname, the turbofish (::<>). "Fighting the borrow checker" needs no explanation for anyone who has written Rust, and no explanation will help anyone who hasn't. For most normal programmers, Rust is unreadable, and unwritable too. It isn't a language you write so much as a language you negotiate with.
And then there's functional programming in general.
My position is simple: humans don't think like that. We think in steps. Every set of instructions people have ever written for one another, from recipes to flat-pack furniture to the checklist a pilot runs before takeoff, is a sequence of steps that changes the state of the world. Nobody's grandmother ever wrote down her pancake recipe as a fold over a list of ingredients.
We also think in mutable state, because the world is mutable state. As an amateur physicist, I can report that the universe has been updating itself in place for 13.8 billion years without once returning a new copy. (The many-worlds interpretation disagrees. I suspect it was proposed by a Haskell programmer.)
Functional programming asks you to give up both. No steps, only expressions. No state, only new values. Recursion instead of loops. Point-free style, which is the art of writing a function without ever naming the thing it works on. Haskell's lens library, which exports well over a hundred operators, including <<%@~, <<<>:~ and ^@?!. And monads, about which Douglas Crockford observed that once you finally understand them, you lose the ability to explain them to anybody. Write-only memory, installed in a human head.
Then there are the chains. A pipeline of functions, each handing its result to the next, reads beautifully in a conference talk. Here are six steps of one:
const top = orders
.filter(o => o.status !== "void")
.map(o => ({ ...o, total: o.lines.reduce((sum, l) => sum + l.qty * l.price, 0) }))
.filter(o => o.total > threshold)
.sort((a, b) => b.total - a.total)
.slice(0, 10)
.map(o => `${o.customer.name}: ${format(o.total)}`);
In production, the chain is twelve steps long, and one day an exception comes out of step twelve. Now you're debugging twelve anonymous functions at the same time. None of them has a name, none of the intermediate results was ever stored anywhere you could look at it, and the stack trace is mostly the library's plumbing. A chain like that is illegible when you read it and useless when you debug it, and worse than useless, because it hides exactly what you need to see. Your only option is to break the chain apart and add temporary variables, logging and breakpoints, like a normal person. In other words, you rewrite it imperatively, just long enough to find the bug.
That's why imperative programming won. Lisp has been around since 1958, ML since 1973 and Haskell since 1990, and each of them has been the language of the future for longer than most of its users have been alive. Even Haskell gave in. It has a whole notation whose job is to make functional code look like a sequence of steps, and Simon Peyton Jones, one of its designers, called it "the world's finest imperative programming language". He meant it as a compliment. I'm taking it as a confession.
Functional programming does have one runaway success with ordinary humans, and that's Excel. More people write Excel formulas than every other programming language combined. Now open a spreadsheet somebody else built and tell me what this does:
=IFERROR(INDEX($B:$B,MATCH(1,($A:$A=E2)*($C:$C>F2),0)),"")
Excel isn't the exception that proves humans think functionally. It's the most successful write-only language in history.
I'll make a confession of my own, though. The mainstream helped itself to the good bits, and so have I. My ideal language doesn't exist yet, but it already has map and filter. Its transactions borrow retry and orelse straight from a Haskell paper that Peyton Jones co-wrote. Its sum types, exhaustive matching and errors-as-values come from the ML family, by way of Rust, whose first compiler was written in OCaml. The functional crowd built the best checkers in the business, and then used them to write code nobody can read. I'm stealing the checkers and leaving the code. It's a gateway drug, I know. I can stop any time I want.
The determined lunatic
None of this requires an exotic language. Any language becomes write-only in the hands of a sufficiently determined lunatic.
You've met them. One wrote a 300-character one-liner because it saved declaring a variable. Another read Roedy Green's classic "How To Write Unmaintainable Code", subtitled "Ensure a job for life", and mistook it for a tutorial. A third spent their weekends on the International Obfuscated C Code Contest, which has been running since 1984, and on Brainfuck, a language with eight commands and a name that doubles as a review, and then brought both hobbies to work on Monday.
Back in 1983, Ed Post observed that the determined Real Programmer can write FORTRAN programs in any language. The determined lunatic can write Perl in any language, and usually does.
Brian Kernighan explained the problem with these people decades ago: "Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?" Of all write-only code, that's the kind I hate most. Nobody forced it on anyone. The lunatic chose it.
Who pays
Write-only languages and determined lunatics make the same trade. They save the writer a little effort and bill it to every reader who comes after, with interest. Code is written once and read for years, so that's a terrible deal. My approach to unit testing refuses it outright. It even prefers copy-paste to clever helpers, because boring is the point.
It's the point of my ideal language, too. My early notes on it say: "We don't want to make the code reader have to ponder the meaning of an expression." So it has no truthiness, no implicit conversions, no shadowing, no abbreviations and no macros. You never need to remember an operator precedence table, because mixing operators requires parentheses. I learned that one the hard way, looking over the shoulder of a developer who couldn't find a bug that was hiding in the precedence table. There's one way to write each thing, and every decision has a name. Each of those rules costs the writer a few keystrokes. Each of them saves every reader a ponder.
Then the writer changed
Where I stand on this is no secret: humans have no business writing code any more. My agents write it. I read it, or at least the parts my tooling decides I should see.
That ought to have been the end of write-only languages. An AI can read Perl, mostly. It could probably even write Perl that was partially legible. It reads regular expressions without moving its lips. And the lunatics have lost their motive: an AI has no ego to feed and no job to secure, and it doesn't write 300-character one-liners unless you ask it to.
It wasn't the end, for two reasons. The first is that I still have to read the code. Writing has become nearly free, and reading hasn't. My attention is now the scarcest resource in the whole process, and a write-only language wastes it on every line.
The second is that AIs have a write-only problem of their own. It looks nothing like ours.
Trust-only languages
An AI doesn't struggle to read code, and it doesn't struggle to write it. What it struggles with is knowing whether what it wrote is right. It writes fluently, confidently and fast, and every so often it's wrong with the serene confidence of a GPS steering you into a lake. Less often than it used to be, but still.
What an AI needs from a language, then, is to be told, early and specifically, when it's wrong.
The languages humans love are built on the opposite principle. Python's community likes to say that "we're all consenting adults here", which is why nothing in the language actually stops you from touching a private attribute. JavaScript will add an array to an object and cheerfully hand you back a string. Ruby was designed to make programmers happy. And the ANSI C committee wrote the whole philosophy into C's official rationale, in plain English: "Trust the programmer."
That was sound advice when the programmer was Dennis Ritchie.
My programmers are brilliant, tireless interns who will cheerfully find a way around any rule you give them, up to and including deleting the test that said no. They are not Dennis Ritchie, and they're not even consenting adults.
For an AI, a relaxed language is write-only in the Signetics sense. Code goes in, and nothing comes back out. No complaint, no warning, no "did you mean". Just silence, all the way to production, where the silence is finally broken by a pager. (For younger readers: a pager was a little box on your belt that beeped when someone needed you, usually at three in the morning. It showed a phone number, and you went and found a phone. Mobile phones and text messages replaced it, and smartphones and messenger apps replaced those. The three in the morning part survived.)
That deserves a name of its own. I call these trust-only languages.
A trust-only language is one that takes the programmer's word for it.
In a trust-only language, the only thing standing between what the code says and what the programmer meant is trust. The AI calls a method that doesn't exist, and the language believes it, right up until that exact line runs. Odds are that line is in an error handler, and that it runs for the first time during an outage, because error handlers are the code nobody tests. The AI assumes a value can't be None, and the language believes that too. C goes one better. It's so sure your program never does anything undefined that it optimizes on the assumption.
The purest trust-only language of them all is bash. In 2015, Steam's Linux client shipped a script containing the line rm -rf "$STEAMROOT/"*, directly under a comment that said # Scary!. When the variable came up empty, the script deleted everything the user owned. The programmer knew. The comment knew. The language took their word for it. (It's also not the kind of comment I had in mind.)
Python has mypy, JavaScript has TypeScript, and everything has a linter. They help. But a check that's optional is still trust. You're trusting that somebody turned it on, and that nobody quietly added # type: ignore or as any to make the red go away. An AI told to make the build green reaches for those long before it gets around to deleting the test. That leaves the test suite as the only thing checking anything, and the AI wrote that too.
The too-clever languages are no better off, and I'd hate them anyway. Humans do love some of that cleverness. Rails developers adore methods that spring into existence from a naming convention, and Lisp programmers will tell you that macros are the whole point. But clever code is, almost by definition, code whose meaning differs from its appearance, and an AI goes by appearances. It has read more code than any of us, and it reads and writes what it has seen most. Show it an implicit conversion, an overloaded operator, a macro or a monkey patch, and it will read each one as whatever it looks like, confidently, and be wrong in exactly the interesting places.
My ideal language taught me this, without even existing. In late 2025, I had agents write examples against my specification, which at the time insisted on lowercase type names and -- comments, among other idiosyncrasies. They drifted straight back to the syntax they'd been trained on. An AI doesn't respect your novelty. It quietly averages it away. And nothing caught it, because nothing in my toolchain was checking. For a while there, my nonexistent language was trust-only.
Friction is feedback
Now look at Rust from the AI's side of the table.
The borrow checker that humans spend months fighting is, to an AI, a strict senior reviewer that never gets tired and never lets anything slide. Together with the rest of rustc, it rejects whole categories of mistakes before anything runs: the use-after-free, the data race, the null that wasn't supposed to be there, the match that forgot a case. And when it says no, it usually says what to do instead: "help: consider borrowing here". Sometimes it even says "help: consider cloning the value if the performance cost is acceptable", which makes rustc the only compiler I know of that will negotiate on price.
The AI takes that offer every time, of course, and when it gets desperate, it reaches for .unwrap() and unsafe. But in Rust, trust is the exception, and it has to be asked for in writing. Every .unwrap() is a bet placed in plain sight. unsafe is a keyword you can grep for, and even inside an unsafe block, the borrow checker goes on checking every reference. In Python, trust is the default, and it's the checking you have to ask for.
A human fighting the borrow checker gets fed up after the tenth attempt and goes home. Whatever love and admiration they had for Rust is gone by then. Then again, these aren't the people who fill out Stack Overflow surveys. Who still uses Stack Overflow, anyway? The same people who still have Slashdot as their home page?
An AI shrugs and makes the eleventh attempt, and the twelfth, at three in the morning, for pennies, without once losing its temper. What a human experiences as friction, an AI experiences as feedback.
That's why, when I restarted my ideal language for AI programmers, I picked Rust as its first backend and as its first oracle, the independent judge of whether a program's ownership really holds up. I don't enjoy reading Rust. I just couldn't find a stricter reviewer. My own design notes concede that lifetimes are "where agents writing Rust spend most of their failed attempts". But every one of those failures is caught at compile time, which is the only time you ever want anything to fail.
So yes: Rust is write-only for humans, and it's the perfect language for AI. That makes Rust the natural predator in today's language pool. And when a human does need to understand some gnarly piece of it, the AI can explain it to them.
Rust lovers, this is the good news I promised you. Your language won. Not that any of you have any business writing it any more. You're competing in an overflowing market of cheap AI labor that is faster and better than you even think you are.
I do still have to review what the AI wrote, so haven't I just moved the problem? Not really. Most of what makes Rust hard to read is proof: lifetimes, ownership modes, trait bounds. Those are claims the compiler has already checked, and nobody needs to re-check a checked proof. What's left for me is the one question a human is actually needed for: is this what we wanted?
There's a difference between code that's hard to read because it's full of proofs and code that's hard to read because it's full of tricks. You can skip a checked proof. You can't skip a trick. Rust has tricks too, mind you. ? quietly converts your errors, and a macro can hide almost anything. It just has fewer of them, and most of them announce themselves.
Now compare that with reviewing AI-written Python. Python is far easier to read, and I'd have to read every line of it, because nothing else has. In a trust-only language, the human reviewer is the type checker. And as I've established elsewhere, the human reviewer is the slowest participant in the whole process.
I'd rather review AI-written Rust than AI-written Python, and I can barely read Rust.
The map
Put the two ideas together and you get a map. One axis asks whether a human can read the code. The other asks whether the language takes your word for it.
| Humans can read it | Write-only for humans | |
|---|---|---|
| Checks your work | Go, C#, Java, Kotlin, Swift | Rust, Haskell, Scala |
| Takes your word for it | Python, JavaScript, Ruby, PHP, C | Perl, bash, APL, C++, Excel |
Top left is the boring corner. Humans have spent years mocking these languages for their ceremony: Java's AbstractSingletonProxyFactoryBean, Go's if err != nil on every third line. But ceremony costs an AI next to nothing, and a reviewer can skim straight past it. The boring languages may be having the last laugh. They're just typing it out in full.
Top right is Rust's home: write-only for humans, excellent for AI, and fine for a reviewer, as long as the hard parts are proofs rather than tricks. Haskell lives there too, as long as nobody imports lens. Scala lives there uneasily, because its implicits are tricks.
Bottom left is where humans are happiest. They love these languages for exactly the reason they're weak for AI: they never argue.
Bottom right is Perl's home, and it has company. Write-only means no human can check the code. Trust-only means no compiler can. That leaves exactly one party checking it: the AI that wrote it.
The trade is off
Write-only languages trade the reader for the writer. Trust-only languages trade the checker for the writer. Both trades were made for the benefit of a writer with Larry Wall's three great virtues: laziness, impatience and hubris.
The new writer is never lazy and never impatient. It does have the hubris. That's what the compiler is for.
So the trade is off. The writer doesn't need the favors any more. The reader and the checker do. If I were designing a language from scratch today, and I am, I'd want Python's readability with Rust's suspicion.
My ideal language keeps its syntax boring, borrowed from Rust, Swift and Kotlin, because that's what agents already write and reviewers already read. It spends its novelty on what the compiler can check. Every failure is in the signature. Every wait has a deadline. Anything that asks for trust has to say so, in a closed vocabulary, with a reason, and the toolchain always shows it to a human. Every error that says no also says what to do instead, because a rule that only says no gets routed around.
A language for machines to write and people to read. Neither write-only nor trust-only.
Back in 1972, a joke chip made it into a data book because it passed through a long chain of approvals and none of them checked anything. Fifty-four years later, we can automate that chain. One AI writes the code, another reviews it, a third writes the tests, and in a trust-only language, every link in the chain takes the previous one's word for it.
We'll find out it's broken the same way Signetics did.
When the customers start calling.
This time, they won't be asking for pricing.