Heroes / Perché / i no

I no

Le cose che Heroes ha valutato e ha rifiutato: che cosa, perché in parole semplici, chi ha detto no, e che cosa dimostrerebbe che il no era sbagliato. I sì di un linguaggio si leggono nella specifica; i no si vedono solo se qualcuno li scrive.

Chi dice no

Ogni modifica al linguaggio passa da un panel di cinque seggi, descritti nella pagina del progetto: ognuno riceve materiale diverso, misura invece di dare un parere, e quattro possono porre un veto. Un veto chiude la proposta, e nessun prezzo la riapre. Ma nessun rifiuto esce dalla seduta come definitivo: viene adottato come provvisorio, in attesa della parola dell’autore. Un no diventa un no quando l’autore lo ratifica, e ogni no di questa pagina porta a registro quella ratifica, con la sua data. Dove il panel gli ha lasciato accanto un’alternativa più prudente, ha scelto la più solida.

Tre regole del procedimento sono sue. Mai un compromesso: il panel prende la soluzione più solida e completa, mai la più economica né una via di mezzo, e per questo un no qui non viene mai dimezzato. Ogni no nuovo deve nominare il programma o il fatto del compilatore che lo smentirebbe, così chi legge può verificarlo invece di fidarsi: la regola l’ha proposta il guardiano della specifica e l’autore l’ha resa vincolante. E ogni cosa rimandata riceve un verdetto con una data: da quella decisione, in un solo giorno, cinque cose che aspettavano da settimane sono diventate no. Un no è toccato anche a una proposta sua: aveva chiesto una seduta contro il parere dell’assistente, il panel ha rifiutato la proposta e ha trovato che il meccanismo richiesto esisteva già, e l’autore ha approvato il rifiuto.

I no più vecchi, dal null alle macro agli indici che partono da uno, stanno nel documento di progetto dal primo giorno, nel primo commit, la prima modifica registrata del repository, che porta la firma dell’autore ed è anteriore a qualunque seduta. Sono nati prima della regola su come smentirli, e questa pagina non finge che la portino.

I no, dal più grande

  1. Niente libreria standard Tutto ciò che va oltre la specifica, dai file ai socket ai database, arriva da librerie C che dichiari tu. È il vincolo fondativo del linguaggio e l’ha posto l’autore prima di ogni altra cosa: ogni funzione fornita in più è un impegno di manutenzione permanente e una concorrente delle librerie che esistono già. Il pragmatico del C ne è il custode a ogni seduta: compila il codice C che un binding richiederebbe, oppure dimostra perché non compila. È un non-obiettivo dichiarato nel documento di progetto, fuori dalla tabella dei rifiuti, e non porta un falsificatore scritto. Il confine con il C
  2. Niente null L’assenza è un tipo, i64?, non un valore che può nascondersi in qualunque variabile. La ragione a registro è una frase: l’errore da un miliardo di dollari, e i linguaggi che hanno provato ad aggiungere dopo la nozione di assenza ci hanno messo anni. Sta nel documento di progetto dal primo giorno. Il puntatore nullo del C conserva il suo nome, nullptr, e resta al confine. Come si gestisce un errore
  3. Niente eccezioni Un errore è un valore che la riga mostra, T?, non un salto invisibile che risale il programma. La ragione a registro: un flusso di controllo che non si vede produce percorsi non gestiti che nessun compilatore segnala, e attraversare il confine con il C con un’eccezione in volo è un comportamento indefinito, cioè il C non promette più niente su quello che succede. L’errore è un valore
  4. Niente ereditarietà, niente overloading, niente conversioni automatiche Quattro no con una sola ragione: una riga deve valere quello che dice, senza che il suo significato dipenda da qualcosa scritto altrove. Con l’ereditarietà, per capire this.save() devi risalire una catena di classi che non vedi. Con l’overloading degli operatori, a + b può fare qualunque cosa, definita da un’altra parte; con quello delle funzioni, quale versione gira dipende da una scelta che sulla riga non si vede. E con le conversioni automatiche, f(x) funziona per una ragione invisibile.
  5. Niente macro Il codice che leggi è il codice che gira. Una macro è codice che si espande in altro codice, o che estende la sintassi, e dopo di lei le due cose non coincidono più. Il no sta anche nella regola di lavoro del progetto su che cosa da Nim non si copia mai: macro, template, sistemi di effetti, e identificatori che ignorano la differenza fra fooBar e foo_bar.
  6. Niente shadowing, niente stato globale modificabile, niente indici da uno Tre no piccoli con la stessa radice: quello che chi scrive crede di leggere deve essere quello che c’è. Con lo shadowing, cioè un nome che ne copre un altro uguale, credi di riferirti a una variabile e ne colpisci un’altra. Con uno stato globale modificabile, il significato di una riga dipende da un contesto che non si vede. E con gli indici che partono da uno, ogni passaggio verso il C diventa uno sbaglio di uno, silenzioso.
  7. Niente closure Una funzione che porta con sé le variabili intorno non entra: ogni funzione ha un nome e una dichiarazione che puoi leggere. Il no è stato deciso sul costo, misurato e non stimato, da tutti e cinque i seggi. L’ingegnere del compilatore ha contato centinaia di punti del compilatore da riscrivere, in decine di moduli. Il guardiano della specifica ha pesato le frasi che servivano e non stavano nel bilancio. Il pragmatico del C ha compilato una closure contro una libreria vera e l’ha vista passare ogni controllo e andare in crash appena eseguita, perché una funzione che porta valori occupa più spazio di quello che il C si aspetta. E il guadagno era piccolo: nel compilatore, una funzione viene passata a un’altra in meno di cinque posti. Lo smentirebbe un programma di cui il compilatore ha bisogno che una funzione col nome non riesce a esprimere, insieme a una rappresentazione compilata in cui una closure ha un solo tipo e otto byte al confine con il C. Le funzioni come valori
  8. Niente trait, niente interfacce Un insieme di funzioni che un tipo promette di avere, e una generica limitata ai tipi che lo promettono: non entra, perché il buco che riempirebbe è largo due operazioni e il codice ne vuole una sola. La seduta ha compilato otto casi: quasi tutto quello per cui altrove servono le interfacce qui funziona già, passando la funzione come argomento. Restano fuori l’ordinamento e la stampa di un tipo generico, e nel compilatore nessuno dei ventisei ordinamenti ordina un record. L’ingegnere del compilatore ha posto il veto sulla forma piena, fra le novecento e le millequattrocento righe e senza un posto dove far vivere la regola che vieta due definizioni per lo stesso tipo; il lettore ha posto il veto sull’alternativa più leggera così com’era scritta. Lo smentirebbe un programma di cui il compilatore ha bisogno che deve smistare su una collezione i cui tipi non si conoscono dove viene costruita, e che una variante non riesce a portare.
  9. Niente reflection, niente annotazioni inventate Un valore non può chiedere a se stesso di che cosa è fatto, e un programma non può inventare etichette da attaccare a una dichiarazione. Il no alla reflection è unanime, con i veti dell’ingegnere del compilatore e del pragmatico del C, e la ragione è la disposizione in memoria: mettere il tipo dentro il valore cambia la dimensione di ogni record e il modo in cui ogni chiamata al C passa i suoi argomenti, e una libreria grafica che aspetta otto byte ne riceverebbe sedici. Il no alle etichette è unanime perché nessuno potrebbe leggerle: niente macro, niente calcolo a compile-time, niente reflection, quindi un’etichetta che nessuno legge è un commento vestito da garanzia. Lo smentirebbe, per la reflection, un linguaggio compilato che scriva i metadati solo per i tipi che un programma chiede, con un binario cresciuto meno del due per cento; per le etichette, un lettore: un consumatore che esista nel compilatore e trasformi un’etichetta sconosciuta in un errore. I valori, fino in fondo
  10. Niente borrow checker Il no che è scaduto ed è rimasto in piedi. Un borrow checker è il controllo che segue chi tiene un riferimento a che cosa, e per quanto. Per settimane la ragione scritta era: non serve, perché ogni valore è una copia indipendente e non c’è niente da controllare. Poi una seduta ha misurato che la frase era falsa alla lettera: due copie di un record che tiene un puntatore C raggiungono la stessa cosa, e una copia poteva liberare quello che tutte le altre tenevano, con il programma che usciva bene e zero segnalazioni. La ragione è caduta e la riga è rimasta, riscritta sotto con la data: il no oggi sta sul costo. Un controllo sulla memoria del C avrebbe bisogno di fatti che nessun header dichiara, e un controllo che li indovinasse rifiuterebbe tredici funzioni su diciassette del binding SQLite del repository, quattro delle quali non toccano niente. Quello che ha chiuso il buco è una parola, consumes, che ne rifiuta due su diciassette. Lo smentirebbe una regola sugli alias di un record che tiene un puntatore, prezzata e compilata. Gli handle
  11. Niente modulo raw Un modulo dove vivrebbe tutto ciò che rompe le garanzie del linguaggio, così che un file è pericoloso solo se lo importa: non entra, perché il documento di progetto aveva già deciso il contrario, in un’altra pagina. Un linguaggio senza libreria standard mette un puntatore in ogni binding, e un marchio che ogni binding porta non dice niente. La seduta ha contato i puntatori nudi che escono da un gruppo nel resto dei programmi d’esempio, e il numero è zero: la forma handle li ha presi tutti. Tre veti, dell’ingegnere del compilatore, del lettore e del guardiano della specifica. Il lettore ha trovato l’obiezione più netta: una lettura attraverso il modulo avrebbe tolto una garanzia invece di recintare un pericolo, perché lo stesso accesso attraverso un array di byte si ferma se esce dai limiti. Il pragmatico del C ha compilato la proposta alla lettera. Lo smentirebbe un programma di cui il compilatore ha bisogno che deve leggere, scrivere o convertire un indirizzo in un modo che nessuna dichiarazione riesce a esprimere.
  12. Niente rilascio legato al tipo Scrivere sul tipo di un handle chi lo chiude, così che il compilatore lo chiuda da solo quando esce di scena: non entra, perché la proprietà è della chiamata e non del tipo. La stessa connessione viene consegnata da una funzione della libreria e solo mostrata da un’altra, e un marchio sul tipo non può sapere quale delle due l’ha data. Il pragmatico del C l’ha compilato: con il rilascio automatico, un programma che legge tre righe ne legge zero e riferisce che è andato tutto bene. L’ingegnere del compilatore ha posto il veto di solidità, l’unico formale della seduta, dopo aver inserito a mano quella riga nel C del binding e aver visto la connessione chiusa prima del primo uso. Una perdita di memoria si può contare; una risposta sbagliata che dice che è andato tutto bene no, e il progetto mette questa differenza sopra ogni costo. Lo storico ha trovato un solo linguaggio che lo faccia, e paga il conto marcando ogni riferimento. Lo smentirebbe un fatto leggibile nell’header che separi una funzione che dà da una che presta. Le tre parole
  13. Niente obbligo dedotto, niente marchio su un ptr Se il binding dice come si chiude un tipo di handle, il compilatore potrebbe dedurre da sé quale chiamata te ne consegna uno. Non entra: il pragmatico del C ha aperto diciannove header di librerie vere, e in dodici la stessa libreria consegna un handle da tenere con una funzione e da guardare soltanto con un’altra, senza dirlo. In uno di quegli header le due funzioni stanno su righe adiacenti e differiscono per una cifra nel nome. Un obbligo dedotto male non dimentica un rilascio: ne impone uno su qualcosa che non è tuo, e questo è corrompere la memoria, non perderla. Quattro veti, di tutti i seggi che ne hanno uno. Al suo posto la parola si scrive: acquires, con il nome di chi riprende l’handle. E la parola su un ptr nudo è rifiutata a sua volta, con i veti dell’ingegnere del compilatore e del pragmatico del C: lì il compilatore non avrebbe dove leggerla, e obbedire a un marchio sbagliato è stato misurato peggio che ignorarlo. Lo smentirebbe una regola, derivata dal solo testo dell’header, che separi le due funzioni adiacenti con zero falsi sui dodici. Le tre parole
  14. Niente copertura per riga Uno strumento che dice quali righe del programma i test hanno eseguito e quali no: è il primo no a uno strumento e non a una forma del linguaggio, e non entra perché il numero misurerebbe la colla e la riporterebbe come programma. La seduta ha compilato un binding SQLite vero con la copertura accesa e ha letto il rapporto: la libreria, che per questo linguaggio è il programma, non compare mai, e le funzioni di controllo che il compilatore emette perché clang verifichi gli argomenti segnano zero per cento, perché non vengono mai chiamate. Lo strumento che Heroes ha già, heroes mutate, introduce un errore alla volta in programmi che funzionano e conta quanti il compilatore ne prende, e arriva dove la copertura non arriva. Lo smentirebbe uno strumento che attribuisca le righe eseguite alla libreria C che un programma chiama senza che il programma la compili dai sorgenti, oppure una classe di difetti che la mutazione non raggiunge e che un rapporto per riga avrebbe nominato. La misura
  15. Le prestazioni non sono mai una giustificazione Dove una scelta scambia velocità con semplicità, vince la semplicità, ogni volta. È dell’autore, in due frasi: le prestazioni non sono un obiettivo, e non devono diventare un soffitto. La lentezza non è per questo accettabile, e un costo che impedisce a un programma necessario di girare torna al panel come bisogno del compilatore. È un non-obiettivo dichiarato nel documento di progetto, fuori dalla tabella dei rifiuti, e non porta un falsificatore scritto.

La tabella dei rifiuti nel documento di progetto tiene anche i no più tecnici, ognuno con la sua ragione e, dove è nato dopo la regola, con ciò che lo smentirebbe: una parola per nascondere una dichiarazione agli altri moduli, perché quello che aggiungerebbe è una prestazione e non una garanzia; una seconda grafia per costruire e leggere un caso di variante con lo stesso disegno, perché in questo linguaggio niente si smonta per pattern e la simmetria creerebbe la cosa che dice di togliere; un campo o un caso privato; l’inferenza dei tipi a distanza; i sottotipi; le coroutine; la sintassi fuori dall’ASCII. Il documento e i verbali sono nel repository, aperto in lettura.

Un no può scadere

Un no vale finché la sua ragione resiste a una misura. Il borrow checker qui sopra ha visto cadere la ragione e restare il no. E una seduta convocata per tutt’altro ha riletto tre rifiuti, la reflection e le etichette fra questi, e ha trovato tre ragioni false sotto tre risposte giuste: ha scritto sotto ciascuna la ragione vera, con la data. La tabella non si riscrive mai: una frase smentita resta, e la correzione le va sotto.

Rimandato non è rifiutato

Accanto alla tabella dei no c’è una lista di cose rimandate: perdono solo sulla semplicità, e aspettano. Per decisione dell’autore ogni voce ha ricevuto una data e uno di tre verdetti: entra, con un suo pezzo di lavoro; è rifiutata per sempre, e il rifiuto nomina ciò che lo smentirebbe; oppure aspetta ancora, con una condizione che qualcuno possa controllare. Cinque voci di quella lista sono diventate no in un solo giorno, e stanno qui sopra. Aspettano ancora, ciascuna con la sua condizione: gli alias di tipo, i test scritti nei commenti, le regole di conversione fra interi di larghezza diversa, la concorrenza dentro il linguaggio, e un secondo modo di produrre il programma finale che non passi dal C. Due cose sono state lasciate di proposito senza casella, né sì né no né dopo: il calcolo a compile-time, perché il C che il compilatore emette ne fa già un pezzo, e i blocchi inline, perché perdono anche sulla leggibilità, che è un veto e non un prezzo.

Il perché di quello che c’è: le regole del linguaggio. Chi giudica: il panel, seggio per seggio. E tutto il linguaggio in un file solo, dove stanno tutti i sì.