Language · 2026
The Heroes programming language
Small enough to fit in a prompt, real enough to compile itself.
At the end of a book on the history of programming languages, one question stayed with me that the book itself never asked: how does a language come about today? I tried to answer by writing one.
The book came first, and the order matters. Twenty-one chapters spent inside FORTRAN, ALGOL, Lisp and Simula leave you with two things: the urge to try, and one excuse fewer for repeating mistakes that had already been made once. I had never written a compiler before this one.
It is called “Heroes”, after the album David Bowie recorded in Berlin in 1977. The quotation marks are his, put there to undercut the word, and they stayed. The rule the project set itself is that the personality stays in the packaging: you will never meet Bowie in an error message.
A real session
The compiler builds a file, out comes an executable and the calculator answers. The rainbow that closes the session is another program’s output, not the page’s.
What makes it different
Heroes is compiled: what you write becomes an executable, and there is no runtime to install. It carries no standard library, on purpose, because maths, files, networking and databases all come from the C libraries the world has been using for fifty years. And it has no null, no exceptions and no garbage collector, the three things that usually bring a program down while it runs.
The bet, though, is elsewhere. The whole language fits in five pages, so it goes into a prompt with room left for the program, and its syntax is chosen so that the mistakes machines make most often do not compile: two arguments of the same type swapped, a case left out, a name never declared. The error arrives with the repair already written in.
A complete program needs nothing around it: no imports at the top, no project file listing the other files.
function main()
print("we can be heroes") The unusual part shows when something goes wrong. This program reads a number out of a string, one digit at a time, and every step can fail: a character that is not a digit, a string with nothing in it.
# Parses a decimal digit. Fails on anything else.
function digit_of(c: u8) -> i64?
if c < '0' || c > '9'
return fail("not_a_digit", "expected 0-9")
return ok(to_i64(c - '0').must())
# Reads a whole non-negative number. Empty input is an error, not a zero.
function number_of(text: str) -> i64?
if text.len() == 0
return fail("empty", "there is nothing to read")
total: i64 @ 0
for i in range(from: 0, to: text.len())
total @ total * 10 + digit_of(text[i])?
return ok(total)
# The `?` above is the whole point: three lines, no error handling in sight,
# and the first bad character stops everything.
function average_of(parts: [str]) -> i64?
sum: i64 @ 0
for p in parts
sum @ sum + number_of(p)?
if parts.len() == 0
return fail("empty", "no parts to average")
return ok(sum / parts.len())
# Four ways to read a fallible value, in the order you reach for them.
function report(text: str) -> str
match number_of(text)
.ok v => return "read " + v.to_str()
.err e => return "failed: " + e.code + " (" + e.msg + ")" There is no null here and there are no exceptions. A function that can fail says so in its type, that question mark after i64, and a failure is an ordinary value carrying a code and a message. A question mark after a call hands the failure up to the caller, with no error handling written anywhere, and the match at the end forces you to look at both outcomes: the one that worked and the one that did not.
Today the Heroes compiler is written in Heroes and compiles itself. It is not packaged: no installer and no releases, so the way in is a C compiler and one command.
The language has a site of its own, with the examples, the measurements and the record of every decision taken along the way.