Sviluppare i propri linguaggi - Perché e come
Ciao a tutti, cari colleghi programmatori.
Approfitto di questo primo post per presentarmi brevemente: mi chiamo Davide e, dal 1984 per lavoro sviluppo software. All'inizio degli anni '90 ci fu il boom dei CASE, Computer Aided Software Engineering, e così da allora mi occupo quasi esclusivamente dello sviluppo di software che scrivono, o aiutano a scrivere, software.
Ma veniamo al topic di questo post:
sviluppare i propri linguaggi, cioè realizzare linguaggi di programmazione personalizzati.
Spesso nell'immaginario collettivo, quando si parla di linguaggi di programmazione custom, ci si immagina di farsi il proprio linguaggio ed utilizzarlo al posto di Java, del C o del BASIC. Sì, può essere divertente come esperimento, ma commercialmente inutile: è come se un tassista si autocostruisse l'automobile invece di usarne una già in commercio.
I linguaggi custom sono definiti e sono alla base di un modo più evoluto di sviluppare software.
Quando noi scriviamo un software, traduciamo delle specifiche che parlano di argomenti del cliente (ad esempio, fatture, fornitori e leggi fiscali), area definita
business domain, in concetti inerenti il linguaggio di programmazione che stiamo usando (ad esempio, tabelle SQL e metodi Java), area definita
application domain.
I programmatori sviluppano delle tecniche mentali ripetitive, tramite le quali un certo tipo di concetto nel
business domain scatena una serie di azioni nell'
application domain (ad esempio realizzare una tabella, scrivere dei metodi, ecc.).
Appare subito evidente che:
1) la parte implementativa può essere molto diversa a partire dallo stesso
business model: il codice per un applicazione GUI Java con DBMS SQL sarà ben diverso dallo stesso programma scritto in C operante in command-line e con dati su file ASCII, pur essendo identiche le regole e i dati da trattare;
2) ogni modifica nel
business domain provoca una serie di cambiamenti a cascata nell'implementazione;
3) ogni cambio di
application domain, cioè ad esempio l'introduzione di nuove tecnologie dovute a progresso tecnico o ragioni prestazionali, spesso richiede la rianalisi del
business domain (cioè, tradotto in soldoni, la riscrittura ex-novo).
Il "sogno" dei linguaggi domain-specific è molto semplice:
1) si inventa un linguaggio avente i termini e i costrutti necessari a spiegare tutto e solo quanto competa il business model del nostro particolare caso;
2) si realizza un compilatore a partire da quello realizzi l'applicazione, magari passando per la generazione di codice in linguaggi standard.
La tecnica può essere applicata a "matrioska": non solo un linguaggio custom per l'intera applicazione, ma linguaggi custom per problematiche implementative intermedie. Se ci pensiamo, esistono già in commercio "linguaggi custom" dedicati a problematiche particolari. Ad esempio, il linguaggio SQL parla in maniera astratta di dati rappresentati in forma tabellare e query: certo non è un "linguaggio di programmazione" come lo intendiamo di solito ma è adatto ad una serie di casi particolari. Oppure MathLab, che parla la lingua dei matematici.
Se pensiamo ai vantaggi di questa tecnica, sono molto appetibili:
- se cambiano le specifiche, devo solo aggiornare la loro definizione e l'applicazione viene rigenerata;
- se cambio l'implementazione, devo solo aggiornare il compilatore e l'applicazione viene rigenerata;
- posso generare la stessa applicazione con implementazioni diverse: ad esempio, una web e una per smartphone;
- posso usare la specifica iniziale per generare dei test automatici dell'applicazione;
- posso generare della documentazione utente sempre automaticamente aggiornata;
- se c'è un errore nel generatore, l'errore viene ripetuto dappertutto e subito individuato: in generale, la qualità è altissima;
- posso partire con una generazione elementare e potenziarla in seguito in base alle esigenze;
- le specifiche presenti nel business-model sono sempre quelle effettive, dato che esso
è il sorgente: in quanti casi, nelle applicazioni reali, siamo pronti a mettere la mano sul fuoco che, magari dopo 10 anni, l'applicazione attuale sia perfettamente allineata al suo documento Word di specifica (ammesso che ce ne sia uno)?
Perché, allora, ad oltre un quarto di secolo dalla nascita del concetto di "CASE", stiamo ancora scrivendo in C, in Java e in .NET?
La risposta risiede ovviamente negli enormi ostacoli tecnici che si frappongono alla realizzazione di un linguaggio custom.
Innanzitutto il primo grosso problema è
che linguaggio realizzare, cioè sintassi e semantica. Noi siamo abituati ad utilizzare linguaggi già stabilizzati, in cui è provato dal tempo e dall'esperienza che sono adatti ad isolare il dominio di competenza. Infatti un linguaggio deve essere intuitivo, consentire di esprimere concetti senza essere troppo limitante e, allo stesso tempo, senza essere macchinoso.
Ma il vero problema è che scrivere un compilatore per un DSL è materia per specialisti. Infatti le problematiche relative al parsing sintattico, gestione degli errori e generazione degli output sono notevoli e poco conosciute.
Oltretutto, i compilatori per DSL hanno problematiche ancora diverse da quelle dei compilatori per linguaggi generalisti. Ad esempio, un compilatore C parte da una sintassi stabile e standardizzata e il grosso del lavoro è nell'ottimizzazione per le varie CPU. Un linguaggio DSL invece, ha una sintassi più semplice ma molto variabile, perché essa finisce con l'evolversi continuamente insieme allo sviluppo dell'applicazione.
Nessuno lavora definendo al primo colpo la sintassi perfetta e facendo il resto in cascata: man mano che ci si lavora, si capisce cosa funziona bene e cosa meno bene del nostro linguaggio custom e si aggiusta il tiro.
Dai primi anni '90, con l'azienda per cui lavoro abbiamo realizzato numerosi DSL con relativi compilatori provando tutte le tecniche di sviluppo: dal C con lex e yacc, al C++, al perl, passando per TCL e altri linguaggi. Ci siamo resi conto che i linguaggi e gli strumenti tradizionali ponevano alcuni problemi seri e così abbiamo cominciato a pensare ad un linguaggio custom... per realizzare linguaggi custom!
L'obiettivo era quello di
consentire a tutti di svilupparsi facilmente linguaggi custom DSL fornendo uno strumento ed una metodologia per farli.
Nel 1999 è nata la prima generazione di questo progetto, che ha risolto molti problemi lasciandone altri ancora aperti. Nel 2008 è iniziato sviluppo della seconda generazione che è sfociata in un prodotto chiamato
FormalCapture, che la mia azienda ha reso gratuitamente scaricabile ed utilizzabile per fini non commerciali.
Con FormalCapture vengono affrontati numerosi problemi relativi allo sviluppo di DSL:
1)
sintassi - Definire e sopratutto modificare e manutenere un analizzatore sintattico con un linguaggio tradizionale è un lavoraccio. Però anche generatori specifici come lex e yacc non scherzano. Uno dei problemi più grossi e sottovalutati è che si definiscono sintassi pensando di ottenere un risultato mentre invece il loro comportamento effettivo è inatteso e incomprensibilie ai meno esperti. FormalCapture ha un sistema grafico guidato di definizione delle sintassi. La sintassi ha delle (piccole) limitazioni nelle possibili definizioni affinché le sintassi risultanti siano sempre ovvie e prevedibili.
2)
trasformazioni - Il punto veramente critico nella realizzazione dei compilatori sono le fasi di trasformazione dei dati dall'input al risultato finale. Queste operazioni hanno a che fare con strutture ad albero che devono essere analizzate e linkate. Il problema con queste strutture è che quando uno implementa il codice per un nodo, la situazione evolutiva presunta dei nodi circostanti a volte non è quella reale: cioè ci si aspetta che il nodo di fianco abbia un dato mentre invece in certe circostanze non è ancora venuto il suo turno di calcolarlo. O magari si scopre a cose fatte che queste necessità a volte si configurano in un loop e sono di fatto irrisolvibili.
FormalCapture introduce nel suo linguaggio ad oggetti FCL un nuovo concetto denominato "programmazione a fasi". Grazie ad esso le operazioni che possono creare risultati inattesi sono impedite dal direttamente compilatore FCL: lo stato degli altri nodi è dichiarato e certo
al momento della compilazione.
3)
gestione degli errori - Uno dei fatti più sottovalutati è che il grosso del lavoro di un compilatore serio è
segnalare gli errori. Nessuno vorrebbe un compilatore che dica solo "errore" e buonanotte. Un buon compilatore segnala l'errore, spiega in maniera comprensibile in cosa consiste e indica dove trovare nel sorgente il punto in cui è avvenuto. FormalCapture gestisce automaticamente gli errori sintattici e fornisce un tracciamento automatico delle stringhe all'interno del programma. Quindi, quando scriveremo "Errore: il termine 'pippo' non è stato definito", la parola "pippo" sarà automaticamente iperlinkata al punto in cui l'utente ha scritto "pippo" nel suo sorgente.
4)
sintassi grafica - Le sintassi non devono essere necessariamente testuali: a volte dice più un diagramma che cento righe di testo. Per questo FormalCapture consente la definizione di sintassi formali grafiche, composte da simboli programmabili, linee di connessione e campi di testo formali.
5)
separazione codice generante e generato - Nei sorgenti dei compilatori ci sono le istruzioni che generano sottoforma di testo il codice in uscita. Il problema è che entrambi i linguaggi fanno largo uso di keyword, simboli, caratteri di escape e indentazioni che si mischiano in maniera illeggibile: non si capisce più qual'è il sorgente del generatore e quello del codice generato.
FormalCapture fornisce una modalità speciale di inserimento del codice generato, che appare colorato diversamente e non soggetto ad escaping, così che possa essere inserito in chiaro, compreso il corretto indenting.
Ad oggi siamo ormai perfettamente consapevoli della validità del modello operativo implementato in FormalCapture: come collaudo abbiamo realizzato tutti i progetti già fatti in passato impiegando qualche ora e, in certi casi anche pochi minuti, quando i loro predecessori avevano richiesto settimane se non mesi di lavoro.
Ora stiamo introducendo FormalCapture presso diverse comunità ristrette di programmatori per avere il feedback di utenti che non abbiano già una preparazione specifica in merito alla creazione di compilatori per linguaggi DSL, cioè il tipo di utenti a cui FormalCapture è destinato.
Pensiamo anche che possa essere anche
un'opportunità di crescita per chi è nel settore, perché lavorare per DSL è talmente vantaggioso che è solo questione di tempo prima che diventi un tipo di esperienza molto ambito.
Pertanto vi invito, se vi va, a scaricare gratuitamente
FormalCapture, provarlo con i suoi
esempi e farci avere il vostro feedback qui su <i>iprogrammatori.it</i>.
Sperando di non aver abusato del forum e della vostra attenzione con questo lungo post, rimango in attesa dei vostri riscontri e a disposizione per qualunque tipo di domanda.
Saluti
Davide