YAPL: Yet Another Programming Language. Da un po’ di anni a questa parte, con la diffusione di strumenti di sviluppo comodi e semplici per la generazione di interpreti e/o compilatori, si sta assistendo a un’autentica proliferazione di nuovi linguaggi di programmazione che cercano di ritagliarsi uno spazio in appositi ambiti applicativi o addirittura di proporsi come sostituti di altri “storici” e consolidati.
E’ il caso di Rust di Mozilla Foundation, che strizza l’occhio al C con una sintassi che ha molto in comune, sebbene non l’abbia presa in prestito a tutti i costi, proponendo nuovi costrutti e funzionalità che lo arricchiscono per snellire “pattern” di utilizzo.
L’obiettivo dichiarato è quello di poter gestire (meglio) grossi progetti, con un occhio di riguardo alla solidità (la gestione della memoria è molto raffinata, e molti costrutti nascono per evitare errori di programmazione molto comuni che portano a problemi di de/allocazione e buffer overflow), forse memori dell’esperienza accumulata con FireFox.
La semplicità del C è, manco a dirlo, abbondantemente superata e il linguaggio, che all’apparenza non sembra complesso (il paragone col C++ è naturale in questi casi), richiederà tempo per assimilare i tanti strumenti che mette a disposizione.
Da questo punto di vista forse il termine corretto da usare per il C è che in realtà sia “scarno” e non necessariamente “semplice”, anche perché è facile impelagarsi in discussioni filosofiche senza fine sul significato di semplicità e complessità.
Debbo dire in tutta sincerità che l’esperienza mi ha portato a maturare un mio personalissimo pensiero sull’argomento “cosa mi piace di un linguaggio di programmazione”, che è imperniato sul concetto di leggibilità. Oltre che scriverlo, trovo quindi che sia piacevole rileggere il codice, a maggior ragione se, oltre a me che l’ho scritto, dovranno farlo anche altri.
Da questo punto di vista non ho fatto mistero di non apprezzare il C, per la maniacale ricerca della stringatezza nella sua sintassi, che porta a scrivere meno codice (meno caratteri, per la precisione) con lo scopo dichiarato di velocizzare la digitazione (ci si doveva sbrigare a scrivere Unix).
Ricordo, per fare un esempio, che la scelta per l’operazione di assegnazione è stata fatta su base meramente statistica poiché è la più comune, per cui hanno preferito il singolo carattere = al più diffuso (all’epoca, dominata da linguaggi Algol-style & derivati) := (che si ritrova spesso nello pseudocodice).
Rust purtroppo ha seguito la stessa strada, e lo si legge già dalle prime battute nel tutorial introduttivo, dove subito dopo la presentazione di un pezzo di codice che riporta una funzione:
fn fac(n: int) -> int { let result = 1, i = 1; while i <= n { result *= i; i += 1; } ret result; }
si legge a commento:
Also, there’s a tendency towards aggressive abbreviation in the keywords—fn for function, ret for return.
L’elenco delle keyword non mostra, quindi, sorprese:
alt assert be break check claim class const cont copy do else enum export fail false fn for if iface impl import let log mod mutable native pure resource ret true type unsafe use while
Si nota subito un uso smodato delle contrazioni, che raggiunge il suo apice coi due soli caratteri dedicati alla keyword che apre la definizione di una funzione: fn.
Purtroppo quel che si nota in queste scelte è una mancanza di stile, di “filosofia” o potremmo chiamarla anche “identità” del linguaggio, che traspare anche in altre scelte.
Infatti le contrazioni non seguono una logica precisa. In molti casi la parola viene troncata dopo un certo numero di caratteri (alt dovrebbe essere l’abbreviazione di alternative, che sostituisce il costrutto switch di C & co.), mentre in altri viene mantenuta la prima lettera, eliminati alcuni caratteri e lasciata la parte finale. fn è il caso più anomalo, dove alla prima lettera se n’è aggiunta un altra che in qualche modo dovrebbe richiamare alla mente function.
Che l’approccio seguito sia statistico, tenendo fede al numero di caratteri digitati, viene confermato subito dopo quando si parla delle parentesi graffe, divenute obbligatorie per racchiudere i blocchi di codice. L’autore, infatti, si affretta a precisare:
If the verbosity of that bothers you, consider the fact that this allows you to omit the parentheses around the condition in if, while, and similar constructs. This will save you two characters every time.
Certe scelte, però, appaiono strane, e la stessa definizione di funzione sopra riportata ne offre un paio di esempi. Il primo riguarda la specificazione del tipo di dato restituito dalla funzione; troviamo, infatti, il simbolo -> (freccia a destra) usato allo scopo, quando si sarebbe potuto utilizzare nuovamente il : e, al contempo, mantenere una certa “filosofia” del linguaggio.
Decisamente più anomala appare la definizione delle variabili locali, che addirittura introduce e fa uso della keyword let quale “prefisso” del costrutto sintattico che porta non soltanto alla definizione di una o più variabili locali, ma anche alla loro eventuale inizializzazione.
Altri linguaggi, come il Go di cui abbiamo già parlato , hanno preferito far ricorso al già citato simbolo := in contesti simili, ottenendo anche l’agognata riduzione dei caratteri digitati.
Purtroppo l’errore più grave di questa scelta è rappresentato dall’aver trascurato un fatto molto importante: all’interno di una funzione la stragrande maggioranza delle assegnazioni nonché dichiarazioni riguarda le variabili locali e non quelle globali (o di modulo / unit, per chi ha questo concetto), per cui sarebbe stato saggio e auspicabile riservare la normale assegnazione a esse, introducendo apposite keyword (glob? gbal? global sarebbe troppo convenzionale e… lungo) diversamente.
Preciso che lo scopo per cui è stato introdotto il costrutto let riguarda in particolare l’uso della type inference per determinare automaticamente il tipo dell’espressione assegnata alla variabile, in modo da evitarne, ancora una volta, la digitazione. Una cosa di cui, comunque, si sarebbe potuto far carico il compilatore. let viene più raramente usato anche per spacchettare tuple (sequenze) di valori in un elenco di variabili.
Sempre all’insegna del risparmio della digitazione dei caratteri è la scelta di definire costrutti sintattici quali l’if, come pure la stessa dichiarazione della funzione, come espressioni, i cui blocchi di codice restituiscono l’ultimo valore calcolato. In questo modo è facile per una funzione evitare persino quei 3 caratteri della già ristretta keyword ret (più lo spazio a seguire, generalmente, e il ; finale: quindi almeno altri 5 caratteri risparmiati!).
Continuando a scorrere il tutorial la situazione non cambia: è un tripudio di costrutti sintattici che mirano alla sistematica riduzione dei tempi di digitazione. Il già citato costrutto alt, che sostituisce il famigerato switch, è uno degli esempi più eloquenti:
alt my_number { 0 { std::io::println("zero"); } 1 | 2 { std::io::println("one or two"); } 3 to 10 { std::io::println("three to ten"); } _ { std::io::println("something else"); } }
la keyword è, di per sé, più corta, ma sono anche completamente sparite le altre keyword case e la label default. Comunque alt non sostituisce soltanto lo switch, ma è un potente strumento basato sul pattern matching che i programmatori Prolog o Erlang apprezzeranno.
In un linguaggio moderno (quindi non si capisce perché Java non le abbia ancora) non potevano non essere presenti le famigerate closure, ma Rust ne mette a disposizione diverse “varianti”. Non essendo una recensione del linguaggio mi limito a riportarne alcuni esempi che mirano a evidenziare il consolidato mantra della velocità di digitazione:
fn call_closure_with_ten(b: fn(int)) { b(10); } let x = 20; call_closure_with_ten({|arg| #info("x=%d, arg=%d", x, arg); });
use std; fn mk_appender(suffix: str) -> fn@(str) -> str { let f = fn@(s: str) -> str { s + suffix }; ret f; } fn main() { let shout = mk_appender("!"); std::io::println(shout("hey ho, let's go")); }
L’aggiunta del solo carattere @ ad fn differenza i due tipi, ma usando la tilde (quindi fn~ anziché fn@) se ne aggiunge un altro ancora…
Lo stesso meccanismo è stato usato per i puntatori. A quelli classici del C, definiti al solito col costrutto *Tipo, se ne aggiungono altri due tipi che fanno uso di @ e ~ al posto dell’* (mentre per la derefenziazione si continua a usare l’operatore *).
Anche le vecchie struct non sfuggono all’opera riduzionista, e si presentano senza apposita keyword, appoggiandosi alla più generale type che definisce un nuovo tipo:
type person = {name: str, address: str};
Per il passaggio di parametri a una funzione, di cui quest’ultima diventa “proprietaria” (facendosi, quindi, carico dell’eventuale deallocazione), fa uso del simbolo + (ma forse sarebbe stato meglio ~, per coerenza con le altre scelte già fatte) prefisso al nome del parametro:
type person = {name: str, address: str}; fn make_person(+name: str, +address: str) -> person { ret {name: name, address: address}; }
Per chiudere, sono presenti anche i generic con la classica sintassi C++/C#/Java/etc. che ben conosciamo, la quale contribuisce ad aumentare ulteriormente l’uso di caratteri non alfabetici all’interno del codice che, a questo punto, è facile immagine disseminato di simboli, e la cui leggibilità risulta tutt’altro che scontata.
Mi chiedo che senso abbia, nel 2012, continuare a effettuare scelte basate sulla ricerca della stringatezza a tutti i costi, quando un programmatore con un po’ di esperienza non soltanto riesce a digitare velocemente sulla tastiera, ma lo fa anche meglio se si limita alle sole lettere, senza ricercare tasti speciali o, peggio ancora, loro combinazioni per far apparire il simboletto desiderato.
Soprattutto in piena era degli IDE, dove il completamento automatico del codice è ormai disponibile in tutte le salse e per tutti i linguaggi, è veramente difficile cercare di giustificare questa sistematica castrazione delle keyword nonché abuso del simbolismo per esprimere concetti.
Non si chiede un ritorno a linguaggi come il COBOL, dove il prerequisito all’utilizzo è rappresentato da una laurea in lettere, ma un giusto compromesso fra le parole, alle quali da esseri umani siamo molto ben abituati, e qualche simbolo che è ormai entrato nel nostro comune utilizzo (uguale, minore, maggiore, punto, virgola, ecc.).
In aggiunta, e per chiudere, da un linguaggio che nasce per essere più solido e per grandi progetti non ci si aspetterebbe l’uso di funzioni come printf del C, i cui parametri variabili possono non coincidere col tipo dichiarato nella stringa di formattazione, ma di appositi costrutti.
Eppure Rust le pesca a piene mani proprio dal C, sebbene offra la possibilità di sfruttare delle macro per controllare che il tipo di ogni parametro sia quello riportato nella stringa (per le stringhe “in chiaro”, cioè definite come letterali).
Macro che mi portano alla mente i tempi degli assemblatori; roba di ben difficile portabilità e, soprattutto, manutenibilità.
Alla luce di tutto ciò credo, quindi, che una sana riflessione sia indispensabile per chi si accinge ad accrescere il già ingente numero di linguaggi di programmazione in circolazione, proponendo qualcosa di più allineato alle moderne esigenze di utilizzo di uno strumento importante come questo, col quale si dovrà passare parecchio tempo…
:-\
sembra il linguaggio di programmazione perfetto per stampare codici fiscali, anzi…
smb lng prf x stmp c.f.
ret
:D
La sintassi potrà sembrare brutta, di primo acchito, però ho sfogliato velocissimamente il tutorial [http://doc.rust-lang.org/doc/tutorial.html] e forse (dico forse) c’è più di un motivo percui la sintassi è saltata fuori così.
Stando al tutorial direi che questo linguaggi assomiglia al C sintatticamente solo in superfice, perchè per quanto supporti il paradigma procedurale pare supporti volentieri anche molto di quello funzionale: prendendo come riferimento Haskell (che è l’unico della categoria di cui conosco un pelo) vedo che ci sono i tipi algebrici, pattern matching, immutabilità di default (la mutabilità è un caso speciale) e altra roba.
Questo Rust potrà anche fare onore nel peggiore dei modi al nome che porta (ruggine? ma lol) però io non taglierei giudizi troppo affrettati: non è che lo veda come un futuro linguaggio mainstream, ma forse un suo circostanziato motivo di essere ce l’ha.
Un grazie di Cuore a Cesare che ha messo per iscritto tante cose che penso anch’io
Il problema della sintassi stringata e’ che troppi programmatori si sentono hacker incompresi che si divertono a scrivere codice non manutenibile (oltre che poco funzionante).
linuga che vanno, linguaggi che vengono, questo qui vivrà solo il tempo di firefox…
@banryu: la mia critica non è legata alle funzionalità di per sé (che per lo più trovo utili e interessanti; poi all’università ho studiato Prolog, per cui ancora oggi ricordo con molto piacere il pattern matching), ma alle scelte “stilistiche” del linguaggio. ;)
@Cesare Di Mauro: sì, beh, in questo senso con me sfondi una porta aperta, neanche io sono un gran patito delle sitassi “azzeccagarbugli”.
Ipotizzavo solo che forse (e sottolineo il forse) la la sintassi è stata determinata da altre considerazioni oltre che i puri e semplici gusti stilistici dell’autore.
La mia esperienza porta a escluderlo. Dove piazzi un simbolo contorto puoi benissimo infilarci una keyword (auto)esplicativa, ad esempio.
Idem per gli altri casi che ho citato. Tant’è che nell’esempio del costrutto alt ci trovi anche la keyword to, quando l’autore avrebbe potuto benissimo utilizzare .. o qualche altro simbolo. Magari gli sarà sfuggito. :D
Mi ricordo che anche il Microsoft Basic sui gli home computer degli anni ’80 aveva le sue abbreviazioni. I Commodore 64 poi considerava solo due caratteri per i nomi delle variabili.
Negli anni ’70, risparmiare caratteri faceva anche risparmiare byte da memorizzare. C’erano varie buone ragioni, ma oggi pare anche a me che abbia poco senso cose come “fn”.
Sarà che mi piace la lettura ma amo i linguaggi il più verbosi possibile, anche nelle keywords.
Anche per me la sintassi stitica è una pessima idea :-D
Quando programmo, passo la maggior parte del tempo a pensare al modo migliore di implementare la funzione che voglio scrivere, e molto meno a scrivere sulla tastiera.
Inoltre, come tutti i linguaggi C-style, pure Rust costringe a usare molte istruzioni anche per compiti semplici, per cui il vantaggio della concisione va interamente perso, dovendo scrivere più codice.
Un VERO salto di qualità sarebbe un linguaggio che implementasse nativamente un equivalente della STL del C++, per dire.
IMHO la vera strada per i linguaggi del futuro è quella presa da Python, PHP o Ruby: il loro vantaggio consiste in istruzioni più potenti, che permettono di fare più cose scrivendo meno codice.
Questo porta a programmi più lenti? Forse, ma le parti di un programma che hanno bisogno di estrema velocità sono molto piccole e possono benissimo essere isolate in librerie scritte in C/asm, e inoltre i compilatori di oggi sono molto, molto più furbi di quelli di una volta e possono fare un lavoro di ottimizzazione migliore. E poi diciamocelo: con i computer di oggi, quanti programmi hanno VERAMENTE bisogno di così tanta CPU per girare? Pochini…
Ma a questi non ha spiegato nessuno che in Eclipse con Alt+Space si attiva l’autocompletamento? ;-)
Più che la stringatezza della sintassi, mi risulta enormemente più utile un IDE con autocompletamento. CTRL+Space e passa la paura. Per non parlare dell’evidenzazione di errori e avvisi in real time.
E a proposito di velocità di digitazione, qualcuno dovrebbe dire a Mozilla che non su tutte le tastiere { e } sono così semplici da digitare come su quella americana…
Buona l’ integrazione di concetti dei linguaggi funzionali( pattern matching, closures, etc).
Piu’ che la sintassi “stitica” avrei preferito maggiore attenzione alla metaprogrammazione statica(vedi nemerle), dove l’ abilità di scrivere meno codice è data dalla generazione di macro sintattiche(che quindi hanno costo computazionale pari a 0 a runtime) in grado di manipolare il type system, e non dalla sintassi perl like che personalmente mi fa girare la testa ogni volta che la vedo.
Mancano anche concetti come gli higher kinded types, che nella mia personale opinione, se gestiti a compile time, permettono veramente di scrivere algoritmi veramenti generici e sintetici, molto piu’ facili da manutenere se scritti con un minimo di attenzione.
ci sono anche similitudini con f#
Sì, prende molto dal paradigma funzionale.
c# : f# = c++ : rust