Perché Heroes
· rebel rebelHeroes nasce da due vincoli: il codice deve essere facile da leggere per un modello e deve poter usare le librerie C.
Gli errori ricorrenti dei modelli · sound and vision
I modelli che generano codice commettono errori ricorrenti: scambiano argomenti dello stesso tipo, dimenticano un caso, usano un nome non dichiarato o una parola chiave di un altro linguaggio. Heroes è progettato per far rilevare questi errori al compilatore.
Un linguaggio progettato perché ogni sbaglio plausibile di un modello sia un errore di compilazione produrrà programmi funzionanti spendendo meno token in totale di un linguaggio convenzionale, anche se ogni singolo programma costa più token da scrivere.
Una parte della tesi è stata verificata introducendo errori plausibili in programmi funzionanti e contando quelli rilevati dal compilatore. Gli stessi programmi vengono poi controllati con le regole specifiche di Heroes disattivate. Il confronto misura l’effetto di quei controlli. Resta da misurare il risparmio totale di token, cioè dei frammenti di testo che un modello legge e genera. I risultati delle prove → Che cosa è misurato, che cosa è stato scritto prima di misurare e che cosa non si sa ancora →
Perché evitare errori può far risparmiare token
costo reale = token del programma × (1 + tasso di riscrittura)
Accorciare la sintassi riduce i token necessari per scrivere un file. Correggere un errore richiede invece altri passaggi: il modello legge il messaggio, rilegge il codice, riscrive una funzione e riprova la compilazione. Evitare una correzione può quindi far risparmiare più token di una sintassi più breve.
Per questo Heroes usa dichiarazioni esplicite e forme brevi per le operazioni comuni. Richiede qualche parola in più dove serve a rendere riconoscibile un errore.
Usare le librerie esistenti
Heroes usa le librerie C per file, socket, database e grafica. Sviluppare e mantenere librerie proprie richiederebbe troppo lavoro per un progetto seguito da una sola persona. Heroes fornisce quindi le operazioni essenziali e usa le librerie C esistenti per il resto.
matematica oltre l’aritmetica → libm file, socket → libc
espressioni regolari → PCRE2 HTTP → libcurl
JSON → cJSON SQL → SQLite
grafica, audio, terminale → SDL, raylib, ncurses
Per usare una libreria, dichiari le funzioni che ti servono. Il nome nella prima riga indica l’header, il file che descrive le funzioni e i tipi della libreria. Clang, il compilatore C, verifica le tue dichiarazioni usando quel file.
extern "math.h" link "m"
function sqrt(x: f64) -> f64
function pow(base: f64, exponent: f64) -> f64
function floor(x: f64) -> f64
Una persona o un modello potrebbe dichiarare una funzione C che non esiste. Il compilatore segnala anche questo errore, verificando l’header della libreria:
error[ffi_unknown_name]: `math.h` declares no `sqrtt` — clang read the header and could not find it
at ffi-demo.hero:2:5
|
2 | function sqrtt(x: f64) -> f64
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: an `extern` names what the header already has (§4.19): check the spelling, or name the header that does declare `sqrtt`
Clang usa l’header reale della libreria. Verifica sia i nomi sia le dimensioni dei tipi nelle posizioni che lo richiedono e segnala gli errori prima dell’esecuzione. Heroes non deve mantenere una seconda descrizione della libreria da aggiornare.
L’header permette di verificare le dichiarazioni. Restano valide anche le regole di Heroes: pow(base:, exponent:) riceve due valori dello stesso tipo, quindi la chiamata deve specificare i nomi degli argomenti.
Le operazioni disponibili comprendono print, len, push, slice, sort, le conversioni tra tipi, map, filter e fold. Ogni aggiunta richiede una decisione registrata.
Usare le librerie C deve essere semplice quanto scrivere in Heroes. Se una libreria non è accessibile da Heroes, chi la vuole usare deve scrivere altro codice C. Il progetto cerca di ridurre questa necessità. I programmi che usano il C →
Perché il compilatore emette C
Heroes traduce il tuo programma in C e affida a clang la produzione dell’eseguibile. Questo permette a clang di verificare le dichiarazioni delle funzioni C sugli header delle librerie a ogni compilazione. Il progetto iniziale prevedeva di generare direttamente codice macchina; è stato modificato per usare questi controlli. La scelta dipende dalla verifica delle librerie, non da un vantaggio di velocità.
Sette parole possono aprire una riga
Un file è una lista di dichiarazioni, e ognuna comincia con la parola che
dice cosa stai dichiarando. constant per un valore fisso,
function, record per un gruppo di campi con un
nome, variant per un valore che arriva in più forme. Altre tre
non dichiarano niente sui tuoi dati: use per un altro file,
test, e extern per quello che viene dal C. La
lista finisce lì. Nessuna classe, nessuna interfaccia, e in nessun punto una
variabile globale che chiunque possa modificare.
# A position on the integer grid.
record Point
x: i64
y: i64
# A rectangle, by two corners.
record Rect
corner: Point
extent: Point
Puoi scrivere le dichiarazioni in qualsiasi ordine. Due funzioni possono chiamarsi a vicenda senza dichiarazioni anticipate. Durante la compilazione, Heroes riconduce il codice a sette costrutti interni, le forme di base usate per rappresentare il programma.
Perché le stringhe hanno dei buchi
Una stringa può portare dei valori, e a trasformare {n} nel testo di n è una f prima della virgoletta d’apertura.
function line(at: i64, of: i64, star: Star) -> str
return f"{at}/{of} {star.name} at magnitude {star.magnitude}"
Perché la f: dentro le stringhe normali la graffa era già in uso, e oltre duecento stringhe del compilatore stesso ne contengono una, quindi una stringa con dei buchi lo dice dal primo carattere e tutte le altre restano come prima. Perché qualsiasi espressione: elencare le forme ammesse costava più token di specifica che ammetterle tutte, e a decidere è stato il conteggio. Cosa può stare in un buco, cosa scrive {{ e cosa dice il compilatore davanti a un buco senza testo stanno nel capitolo. Le stringhe, buchi compresi → L’esempio intero →
Le regole del linguaggio · fashion
- Una riga si capisce guardando quella riga. Il suo significato è
deciso dalla riga e dalla funzione intorno, mai da qualcosa scritto tre file
più in là. Niente ereditarietà, niente macro, niente eccezioni, niente
conversioni automatiche, niente null: l’assenza è un tipo,
i64?. Il puntatore a zero del C conserva il suo nome,nullptr; unptrè un indirizzo nudo che nessun codice Heroes legge, e il puntatore che una libreria C ti consegna porta il nome che la libreria gli dà, così uno non passa al posto di un altro. Ognuno di questi no è a registro, con chi l’ha detto: i no di Heroes → - Un solo modo di scrivere qualunque cosa. Un formattatore con una sola forma canonica, una parola sola per i cicli, e i nomi degli argomenti obbligatori dove i tipi non bastano a distinguerli.
- Messaggi d’errore utilizzabili anche da una macchina. Ogni messaggio indica il problema e la posizione nel codice. Se coinvolge una dichiarazione in un altro punto, indica anche quella. Le correzioni suggerite sono contrassegnate da
certainse sono certe o daguessse richiedono una verifica. Con???puoi lasciare un’espressione incompleta e chiedere al compilatore quale tipo si aspetta. - Un programma non deve corrompere la memoria. Il programma si ferma e segnala il punto dell’errore in casi come questi: un intero supera il suo intervallo, una divisione intera ha divisore zero, un indice esce dai limiti di un array, una porzione di stringa taglia un carattere, la ricorsione è troppo profonda o
.must()riceve un errore. Nelle scelte di progetto, evitare l’accesso a memoria non verificata ha la precedenza sull’eleganza e sulla brevità del codice. - Abbastanza piccolo da stare in un prompt. La specifica resta sotto il limite di 16K token, contati come li conta il modello stesso, attraverso l’API di conteggio dei token di Claude. Il conteggio è registrato insieme al modello che lo ha prodotto, e un test a ogni commit, cioè a ogni modifica registrata, tiene il conteggio registrato sotto il tetto e rifiuta un testo cambiato senza un nuovo conteggio. Puoi passarla intera a un modello. Leggila →
Cosa Heroes fa male
Ognuna è una scelta, e questo è quello che costa.
- Non c’è una libreria standard. Le funzioni predefinite
sono una riga della specifica: stampare, la lunghezza,
push,slice,chars,keys,join,repeat, l’ordinamento, le conversioni numeriche, emap,filter,fold,find,any,allerange, che sono scritte in Heroes a loro volta; più leggere e scrivere un file, gli argomenti della riga di comando edexit. Tutto il resto è una libreria C che dichiari tu, e non ci sono un package manager né un registry da cui prenderla. - Il lavoro in parallelo porta un numero solo. Un thread prende
un
i64e ne restituisce uno. Se metti una lista dentro il tipo di una callback, il compilatore la rifiuta prima che il programma parta, conerror[ffi_type], e la sua nota ti dice di passare al C un puntatore e una lunghezza. Distribuire un array grande su più core non è quello che questo linguaggio fa oggi. - Oltre il confine con il C si controlla quello che hai scritto.
clang verifica che le tue dichiarazioni combacino con l’header della
libreria. Un handle, il puntatore a qualcosa che la libreria tiene per sé,
preso con una chiamata marcata
acquiresviene contato, e il programma si ferma in uscita se uno non è stato restituito; uno ricevuto in prestito non passa a una chiamata che lo chiude. Quello che nessun controllo vede: cosa la libreria fa di un indirizzo una volta che lo ha, e se legge ancora un testo dopo che gliel’hai ritirato. Quella parte resta tua. Cosa il confine controlla → - Alcuni errori nessun controllo li prende. Un codice
d’errore scritto male dentro una stringa resta una stringa valida, e
10dove volevi100resta un numero. La pagina degli errori porta la misura, così la quota che il compilatore prende davvero è un numero che puoi leggere e non una promessa. I risultati disponibili → - I programmi sono più lenti dello stesso programma in C. Ogni indice viene controllato, ogni operazione sugli interi ferma il programma se trabocca invece di dare un numero sbagliato, e un valore condiviso da due nomi viene copiato nel momento in cui uno dei due lo cambia. La velocità non è un obiettivo di questo linguaggio, e un crash o una memoria corrotta sono un bug. Nessun confronto di velocità con un altro linguaggio è stato pubblicato, e questa pagina lo dirà finché non ce ne sarà uno.
- Si scrive di più. Gli argomenti vanno nominati quando due parametri hanno lo stesso tipo, ogni chiamata che può fallire va gestita sulla sua riga, e non ci sono funzioni anonime, ereditarietà, overloading, una seconda variabile sotto un nome già in uso né valori predefiniti. Ognuno di questi è un no con la sua ragione scritta. I no →
- Linux, macOS e Windows, a 64 bit, passando da clang. Heroes gira su macchine a 64 bit e compila attraverso clang, che installi tu: non c’è ancora un installer né un pacchetto. Il browser e i microcontrollori non sono posti dove gira oggi. Per cominciare →
Domande frequenti · quicksand, avoided
Perché niente null, perché gli argomenti nominati, perché ogni assegnamento è una copia, quanto è veloce, cosa costa, se fidarsi di un compilatore scritto da un’AI: le domande che ci si fa prima di provare Heroes hanno una pagina loro, ognuna con una risposta di poche righe. Domande frequenti →
Per proseguire: Tutto il linguaggio è un file solo, abbastanza piccolo da darlo a un modello e lasciare spazio al tuo programma. Tutti i programmi del repository sono scritti così, e quattro comandi costruiscono il compilatore.