Linguaggio · 2026
Il linguaggio Heroes
Così piccolo da stare in un prompt, così vero da compilare se stesso.
Alla fine di un libro sulla storia dei linguaggi di programmazione mi è rimasta addosso una domanda che nel libro non c’era: come nasce, oggi, un linguaggio? Ho provato a rispondere scrivendone uno.
Il libro è venuto prima, e l’ordine conta. Ventun capitoli passati dentro FORTRAN, ALGOL, Lisp e Simula lasciano addosso due cose: la voglia di provarci e una scusa in meno per rifare gli errori che erano già stati fatti una volta. Non avevo mai scritto un compilatore prima di questo.
Si chiama “Heroes” come l’album che David Bowie registrò a Berlino nel 1977. Le virgolette sono le sue, messe lì per smorzare la parola, e sono rimaste. La regola che il progetto si è dato è che la personalità resti nella confezione: dentro un messaggio di errore Bowie non si incontra mai.
Una sessione vera
Il compilatore costruisce un file, ne esce un eseguibile e la calcolatrice risponde. L’arcobaleno che chiude la sessione è l’output di un altro programma, non un effetto della pagina.
Cos’ha di diverso
Heroes è un linguaggio compilato: quello che scrivi diventa un eseguibile, e non c’è nessun runtime da installare. Non ha una libreria standard, di proposito, perché matematica, file, rete e database arrivano dalle librerie scritte in C, quelle che il mondo usa da cinquant’anni. E non ha null, né eccezioni, né garbage collector: le tre cose che di solito fanno cadere un programma mentre gira.
La scommessa, però, è un’altra. Tutto il linguaggio sta in cinque pagine, così da entrare in un prompt insieme al programma da scrivere, e la sua sintassi è scelta perché gli sbagli che le macchine fanno più spesso non compilino: due argomenti dello stesso tipo invertiti, un caso dimenticato, un nome mai dichiarato. L’errore arriva con la correzione già scritta dentro.
Un programma completo non ha bisogno di niente intorno: nessuna riga di import in cima, nessun file di progetto che elenchi gli altri file.
function main()
print("we can be heroes") La parte più insolita si vede quando qualcosa va storto. Questo programma legge un numero dentro una stringa, una cifra alla volta, e ogni passaggio può fallire: un carattere che non è una cifra, una stringa vuota.
# 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 + ")" Qui non esistono il null né le eccezioni. Una funzione che può fallire lo dichiara nel tipo, quel punto interrogativo dopo i64, e l’errore è un valore normale, con un codice e un messaggio. Il punto interrogativo dopo una chiamata passa il fallimento a chi ha chiamato, senza una riga di gestione scritta da nessuna parte, e il match in fondo obbliga a guardare tutti e due i casi: andata bene e andata male.
Oggi il compilatore di Heroes è scritto in Heroes e si compila da solo. Non è impacchettato: niente installer e niente versioni ufficiali, quindi la strada per entrare resta un compilatore C e un comando.
Il linguaggio ha un sito suo, con gli esempi, le misure e il registro di ogni decisione presa lungo la strada.