Programmazione, linguaggi di
I computer «possono fare tutto quello che gli sappiamo ordinare» scriveva nel 1842 Ada Augusta Byron Lovelace, considerata la prima programmatrice della storia. Oggi i computer sono impiegati in innumerevoli campi, dalle previsioni del tempo a Internet, alla videoscrittura, ai videogiochi. Inoltre, moltissimi oggetti di uso comune (telefoni cellulari, elettrodomestici, automobili) hanno un computer al loro interno. In ogni caso, affinché il computer svolga i propri compiti è indispensabile comunicargli le opportune istruzioni.
I linguaggi di programmazione sono i linguaggi artificiali progettati per trasmettere le istruzioni a un computer; essi consentono di formalizzare gli algoritmi in maniera non ambigua e permettono di scrivere i programmi, i quali possono essere eseguiti da un computer, ma devono anche poter essere letti, compresi e modificati da esseri umani. Ogni programma descrive le strutture dati su cui operare, le operazioni da eseguire, i dati in ingresso e in uscita. Linguaggi informatici, per esempio, come HTML e XML, che servono solo per descrivere dati, non vengono in genere considerati linguaggi di programmazione.
La sintassi di un linguaggio di programmazione è molto rigida e non consente le ambiguità tipiche del linguaggio umano, il quale proprio per questo risulta poco adatto a comunicare con i computer. Nonostante questa differenza, la sintassi è uno dei legami più forti fra linguaggi artificiali e linguaggi naturali. Le definizioni sintattiche dei linguaggi di programmazione, nonché le tecniche automatiche di analisi sintattica (parsing) che ne permettono l’implementazione, sono state sviluppate a partire da ricerche in campo linguistico, come la classificazione dei linguaggi formali proposta da Noam A. Chomsky. Dai primi anni Sessanta del Novecento la sintassi della maggior parte dei linguaggi di programmazione è definita esplicitamente mediante un appropriato formalismo, come la Backus-Naur form (BNF).
I linguaggi possono avere sintassi semplici come il Lisp o il Forth, i quali prevedono solo pochi costrutti elementari, oppure complesse come il Perl o il C++, dotati di una grande varietà di segni di interpunzione e simboli con complesse regole di precedenza. In genere, le sintassi più semplici risultano più versatili, mentre quelle più complesse possono riprodurre notazioni già in uso (per es., formalismi matematici o linguaggio naturale) e risultare quindi più comode. Alcuni linguaggi permettono poi di modificare in qualche misura la loro sintassi definendo nuove parole e nuovi costrutti.
La storia dei linguaggi di programmazione copre poco più di mezzo secolo. In questo periodo tre generazioni di programmatori e di informatici hanno sviluppato migliaia di linguaggi, definendo diverse metodologie di programmazione. Vari elementi hanno influenzato questa evoluzione; tra questi vanno considerati in primo luogo lo sviluppo dei computer, l’espansione del loro utilizzo, l’allargamento e il cambiamento della comunità dei programmatori. Non va tuttavia dimenticato il ruolo fondamentale svolto anche dalle aziende che operano nel settore, le quali hanno costituito una spinta importante per la diffusione attuale dei linguaggi di programmazione.
Il primo dispositivo che ricorda i moderni computer, per finalità e struttura logica, è probabilmente la macchina analitica progettata da Charles Babbage nel 1837 e mai costruita. Secondo la visione di Babbage la macchina a funzionamento meccanico, azionata da un motore a vapore, sarebbe stata controllata da schede perforate simili a quelle usate da alcuni telai meccanici. Ada Augusta Byron, contessa di Lovelace, descrive l’implementazione sulla macchina di Babbage di un algoritmo per la tabulazione dei numeri di Bernoulli che viene considerato il primo programma della storia. Un linguaggio di programmazione sviluppato alla fine degli anni Ottanta del Novecento per conto del Dipartimento della Difesa statunitense si chiama appunto Ada in onore della prima programmatrice.
Uno dei primi calcolatori totalmente elettronici, basato su valvole termoioniche, è l’ENIAC (Electronic numerical integrator and calculator). Costruito tra il 1943 e il 1946 per calcolare le tabelle balistiche per l’esercito degli Stati Uniti, esso non viene programmato in senso stretto: per fargli risolvere problemi diversi occorre modificare la posizione di interruttori e cavi elettrici. Nel 1945 il matematico John von Neumann formalizza l’architettura dei computer moderni nella cui memoria, oltre ai dati, viene immagazzinato anche il programma, ovvero l’insieme delle istruzioni da eseguire (lo stesso concetto è accennato in un brevetto di Konrad Zuse del 1936). Quest’idea separa il software dall’hardware, che viene così reso più semplice e affidabile, e permette di migliorare la versatilità dell’intero sistema. Nella memoria del computer le istruzioni sono rappresentate da codici numerici espressi in sistema binario, detti linguaggio macchina. Data la difficoltà di scrivere programmi direttamente in linguaggio macchina, all’inizio degli anni Cinquanta si sviluppano i linguaggi assemblativi, che permettono l’utilizzo di parole facili da ricordare (come add, jump ecc.) al posto dei codici numerici.
Oggi l’utilizzo dei linguaggi assemblativi è limitato ai casi in cui sia richiesta la massima efficienza (come nella programmazione di videogiochi) oppure lo stretto controllo dell’hardware.
I linguaggi assemblativi sono altamente versatili ed efficienti, ma richiedono di ragionare nei termini delle operazioni elementari che il computer è in grado di eseguire: compito laborioso e suscettibile di errori. Inoltre, il codice scritto per un computer non è eseguibile su computer di tipo diverso. Per queste ragioni vengono sviluppati i cosiddetti linguaggi di alto livello: si tratta di linguaggi astratti, più vicini al dominio applicativo che alla struttura del computer.
Già nel 1946 il tedesco Konrad Zuse progetta (ma non implementa) il Plankalkül, linguaggio sorprendentemente moderno il cui campo di utilizzo spazia dal calcolo numerico all’analisi di problemi scacchistici. Nell’immediato dopoguerra, lo straordinario lavoro di Zuse ha scarsa influenza nel mondo della programmazione. Nel 1949 John W. Mauchly inventa lo Short code, un linguaggio interpretato per i calcolatori BINAC e UNIVAC mentre Grace Hopper tra il 1951 e il 1953 sviluppa i primi compilatori.
Il primo linguaggio ad avere una grande diffusione è il Fortran, sviluppato all’IBM tra il 1954 e il 1957 da un gruppo guidato da John W. Backus. Il linguaggio, specializzato per applicazioni scientifiche e ingegneristiche, permette di esprimere formule matematiche con una notazione naturale (per es., 3x4+y si scrive semplicemente 3*X**4+Y, invece di richiedere una sequenza di istruzioni assembler). Il codice prodotto dal compilatore si dimostra quasi altrettanto efficiente di quello codificato direttamente in assembler. Questa caratteristica è determinante per la diffusione del linguaggio in un contesto in cui il tempo di calcolo è una risorsa preziosa. Il Fortran, di cui nel novembre del 2004 è stata rilasciata l’ennesima formulazione standard, può essere considerato il linguaggio di programmazione più longevo.
Nel 1958 John McCarthy del MIT crea il Lisp, il cui obiettivo principale non è il calcolo numerico, ma la manipolazione di espressioni simboliche. Il Lisp utilizza un unico tipo di struttura dati, molto versatile: la lista. Un programma Lisp è costituito da varie unità, le funzioni, anch’esse rappresentate come liste. Il fatto di poter gestire in modo omogeneo strutture dati articolate, unitamente alla capacità di manipolare sia dati che funzioni, rendono il Lisp il linguaggio di elezione per la ricerca nel campo dell’intelligenza artificiale. Il Lisp, utilizzato ancora oggi, ha dato origine a molti dialetti (come Common Lisp e Scheme) e ha influenzato un grandissimo numero di altri linguaggi (Logo, Dylan, Clos, Smalltalk, Ruby).
Nel 1959 un comitato, lo Short range committee, influenzato da Grace Hopper, sviluppa un linguaggio orientato ad applicazioni di tipo gestionale: il Cobol. Esso è rivolto a programmatori senza una specifica preparazione scientifico-informatica e adotta una sintassi molto simile all’inglese, che dovrebbe rendere il codice facile da leggere e da comprendere. Il Cobol vanta ancora oggi una consistente quantità di programmi attivi e una numerosa comunità di programmatori.
Nel 1960 un comitato di scienziati dell’informazione statunitensi ed europei pubblica la definizione dell’Algol, la cui sintassi versatile e sofisticata viene, per la prima volta, specificata formalmente. Il linguaggio diventa uno standard de facto per la pubblicazione di algoritmi ed è così avanzato da essere, secondo un aforisma attribuito a Charles A.R. Hoare, «un gran progresso rispetto ai suoi successori». Relativamente popolare in Europa alla fine degli anni Sessanta, non avrà il successo commerciale del Fortran o del Cobol, ma influenzerà molto la progettazione dei linguaggi successivi (Pascal, C, Java ecc.).
Nei due decenni successivi i tentativi di progettare un linguaggio universale (come il PL/I, sviluppato dall’IBM nella prima metà degli anni Sessanta) coesistono paradossalmente con la moltiplicazione di nuovi linguaggi: oltre 170 nel 1972, di cui circa la metà di utilizzo generale (ovvero non legati a un campo applicativo prestabilito). In questo periodo si studiano, si sperimentano e si diffondono buona parte dei costrutti e dei paradigmi di programmazione oggi utilizzati.
Edsger W. Dijkstra pubblica nel 1968 un articolo che rappresenta il manifesto della programmazione strutturata: un paradigma di programmazione che, fra altre cose, individua nell’uso indiscriminato del goto (l’istruzione che modifica l’esecuzione sequenziale del codice) uno degli ostacoli alla scrittura di programmi facili da capire e modificare. Lo stesso anno Niklaus Wirth crea il linguaggio Pascal che combina le migliori caratteristiche del Cobol, del Fortran e dell’Algol e incorpora i concetti della programmazione strutturata. Creato principalmente come strumento didattico, il Pascal diventa in breve un linguaggio di successo dalla numerosa progenie (tra gli altri, Modula-2, Oberon, Delphi e Kylix).
A Oslo, Ole-Johan Dahl e Kristen Nygaard, dovendo realizzare per il Centro di calcolo norvegese un programma di simulazione di traffico navale, progettano nel 1967 un linguaggio, chiamato Simula, che permette di associare a ogni nave un’entità con caratteristiche e operazioni ben definite. Questa impostazione, elaborata ulteriormente nel linguaggio Smalltalk, sviluppato nel 1976 presso lo Xerox PARC, è la base della cosiddetta programmazione orientata agli oggetti.
Nel 1972 Alain Colmerauer, Philippe Roussel e Robert A. Kowalski sviluppano il Prolog con l’obiettivo di creare uno strumento per la ricerca in linguistica computazionale. Dotato di una sintassi e di una semantica semplici e chiare, orientato alla descrizione del problema più che alla codifica dell’algoritmo di soluzione, viene utilizzato con profitto anche da linguisti senza conoscenze informatiche. È uno dei primi linguaggi logici e probabilmente il più noto. La famiglia di linguaggi basati sul Prolog ha un ruolo centrale nel progetto di ricerca sui computer di quinta generazione, finanziato in Giappone durante gli anni Ottanta e abbandonato negli anni Novanta.
Sempre nel 1972 Dennis M. Ritchie, presso i laboratori Bell della AT&T, sviluppa il C (ispirato al linguaggio B di Kenneth C. Thompson e al BCPL di Martin C. Richards). Progettato per la scrittura di software di sistema, il nuovo linguaggio risulta compatto e molto efficiente e permette un controllo dell’hardware confrontabile con quello dei linguaggi assemblativi, pur disponendo dei costrutti tipici di un linguaggio di alto livello. Nel 1973 il cuore del sistema operativo Unix viene riscritto interamente in C. Nel linguaggio molte funzionalità, come le funzioni matematiche o la gestione dell’I/O, sono delegate a una libreria standard, scritta in gran parte in C. Quest’architettura facilita lo sviluppo dei compilatori e contribuisce, assieme allo stretto legame con Unix, al grande successo del linguaggio. Nel 1978 Ritchie e Brian W. Kernighan pubblicano The C programming language, che servirà per molti anni come specifica informale del linguaggio. L’esempio introduttivo del libro, il piccolo programma che scrive ‘Hello, world!’, è diventato un incipit abituale anche nei manuali di altri linguaggi.
La diffusione tra gli anni Settanta e gli anni Ottanta dei personal computer, macchine dotate (allora) di risorse limitate (poca memoria e ridotta capacità di calcolo) e destinate a un’utenza non specializzata, richiede un linguaggio semplice da implementare e da imparare. Il Basic, sviluppato dieci anni prima (1964) da John C. Kemeny e Thomas E. Kurtz con l’intento di avvicinare alla programmazione persone non esperte di informatica, risulta la scelta ideale. Nel 1975 Bill Gates e Paul Allen, prima di fondare la società Microsoft, rilasciano un interprete Basic per il computer Altair 8800. In breve illinguaggio diventa una dotazione quasi obbligatoria per tutti i personal computer e, all’inizio degli anni Ottanta, milioni di persone possono programmare in Basic. Probabilmente in quegli anni i programmatori in Basic sono più numerosi di quelli di tutti gli altri linguaggi messi insieme. La popolarità del Basic comincia a diminuire verso la fine degli anni Ottanta, con l’aumento delle capacità dei personal computer e la conseguente diffusione di linguaggi più evoluti. Oggi è molto popolare un suo dialetto, il VisualBasic.
Mentre i personal computer favoriscono l’uso del calcolatore elettronico ben al di fuori dell’ambito accademico, la ricerca sulle frontiere del supercalcolo porta alla creazione dei computer paralleli, dotati di più unità di calcolo. Si tratta di una nuova architettura che pone problemi diversi nella scrittura dei programmi e favorisce lo sviluppo di ulteriori linguaggi di programmazione per gestire efficentemente il parallelismo (Occam, HPF).
Alla fine degli anni Settanta i programmi sono diventati così complessi che il loro sviluppo richiede il lavoro coordinato di più persone. Per questo i nuovi linguaggi di programmazione (per es., Modula, ML e Ada) sono progettati in modo da favorire la divisione di un progetto in moduli sviluppabili indipendentemente. Questi linguaggi distinguono la dichiarazione delle funzionalità di un modulo dalla loro implementazione, in modo che sia possibile utilizzare il modulo tramite la sola dichiarazione, senza doverne studiare il funzionamento in dettaglio.
Lo stesso principio viene ulteriormente enfatizzato dal paradigma della programmazione orientata agli oggetti, divenuta molto popolare anche grazie alla grande diffusione del linguaggio C++. Tra il 1972 e il 1980 Bjarne Stroustrup sviluppa una serie di estensioni al linguaggio C collettivamente chiamate ‘C con classi’ e nel 1983 rilascia la prima implementazione del C++. È un linguaggio quasi perfettamente compatibile con il C, di cui mantiene l’efficienza, correggendone alcune carenze. Introduce molti costrutti nuovi fra cui il supporto per la programmazione orientata agli oggetti e per la programmazione generica. Il suo successo sarà confrontabile con quello del C.
A metà degli anni Ottanta le prestazioni dei computer sono aumentate in maniera straordinaria; inoltre si è ribaltato il rapporto fra costo del computer e costo del lavoro. Questi fattori favoriscono lo sviluppo di tecniche che permettono di diminuire il tempo di sviluppo di un’applicazione anche a costo di un’efficienza minore. Fra queste tecniche si annoverano i cosiddetti linguaggi di script.
I primi linguaggi di script, utilizzati principalmente per controllare l’esecuzione di altri programmi, risalgono a metà degli anni Sessanta. Dal 1979 al 1982 Mike Cowlishaw all’IBM sviluppa Rexx, con l’ambizione di farne un linguaggio di script universale: è infatti possibile, e relativamente facile, integrare Rexx in un altro programma in modo da poter controllare quest’ultimo tramite script. Questo linguaggio viene considerato il precursore dei più noti Tcl/Tk e Python.
Uno dei più noti linguaggi di script è Perl, sviluppato nel 1987 da Larry Wall, che era di formazione un linguista. Si tratta di un linguaggio interpretato, particolarmente efficace nella manipolazione di file testuali, che permette di interagire con altri programmi controllandone l’esecuzione e facendo fluire i dati dall’uno all’altro. Si dimostra utile in un campo di applicazioni straordinariamente vasto e influenza un gran numero di altri linguaggi (per es., il recente Ruby, il cui nome si richiama esplicitamente a Perl).
La diffusione del World Wide Web negli anni Novanta ha profonde ripercussioni sul mondo dei linguaggi di programmazione. All’inizio il nuovo medium si presenta come una biblioteca distribuita di pagine multimediali e ipertestuali, essenzialmente statiche. Il grande successo del Web, anche al di fuori dell’ambito accademico, porta diversi cambiamenti: le pagine diventano interattive e rispondono ai comandi dell’utente in modo da rendere la navigazione più semplice e accattivante. Inoltre vengono sviluppate applicazioni che utilizzano le pagine web come interfaccia utente e permettono di fare prenotazioni e acquisti, consultare e modificare database, ecc. A tal fine il programma utilizzato per navigare (detto browser) diventa capace di eseguire altri programmi associati alle pagine web e scritti in linguaggi di programmazione sviluppati a questo scopo (Java, JavaScript, ActionScript). Anche il sistema che pubblica le pagine (il web server) può eseguire programmi, per esempio al fine di interagire con i database. All’inizio vengono utilizzati linguaggi di script già esistenti come Perl o Tcl, a cui in seguito se ne affiancano altri, a volte sviluppati principalmente con questo scopo (PHP, Python, Ruby, Java).
Nel 1995 SUN rilascia la prima versione di Java, un linguaggio ottimizzato per le applicazioni legate al World Wide Web. A partire dallo stesso anno il popolare browser Netscape permette di visualizzare applet: programmi Java inseriti nelle pagine web, che vengono scaricati sul computer dell’utente contestualmente alla pagina stessa e vengono eseguiti immediatamente, senza complicate procedure di installazione. Un applet, in teoria, può essere eseguito su qualsiasi computer e su qualsiasi sistema operativo su cui sia stato installato un browser adatto, come Netscape. Le caratteristiche del linguaggio e il grande supporto da parte di SUN permettono a Java di cavalcare la straordinaria espansione del Web e diventare in breve tempo uno dei linguaggi più popolari.
Nel 2000 Microsoft rilascia il C#, un linguaggio integrato nella sua piattaforma .NET, simile al C++ e a Java. Il linguaggio è diventato uno standard ECMA.
È difficile prevedere l’evoluzione futura dei linguaggi di programmazione, ma è possibile individuare alcuni dei processi che la stanno guidando.
Anzitutto, i linguaggi di script e più in generale i linguaggi dinamicamente tipizzati continuano a guadagnare spazio, favoriti dall’incremento di prestazioni dei computer e dallo sviluppo di nuove metodologie di test del software che rendono meno importanti i controlli in fase di compilazione, propri dei linguaggi staticamente tipizzati.
Inoltre, sono sempre più frequenti applicazioni complesse basate sulla tecnologia del web e questo favorisce lo sviluppo dei relativi linguaggi (Java, C#, Pyhton, JavaScript ecc.). L’attenzione si sposta dal linguaggio in sé alla cosiddetta piattaforma ovvero all’insieme di programmi di supporto indispensabili perché l’intero sistema funzioni.
Si stanno diffondendo poi nuove piattaforme di calcolo di tipo mobile (telefoni cellulari, palmari ecc.), che richiedono, come nei primi anni Ottanta, linguaggi e sistemi operativi portabili e compatti.
Il movimento OpenSource gioca, infine, un ruolo sempre più rilevante nella diffusione di alcuni linguaggi di programmazione.
2. Concetti comuni a tutti i linguaggi
Nonostante profonde differenze, formali e sostanziali, i linguaggi di programmazione si strutturano attorno a un certo insieme di concetti comuni, concernenti i dati su cui il programma dovrà intervenire e le operazioni da eseguire su tali dati.
Tutti i linguaggi di programmazione (a esclusione del linguaggio macchina) permettono di attribuire nomi ai dati, per esempio mediante le variabili, gli argomenti delle funzioni e delle procedure, le costanti con nome. La modifica del valore di una variabile durante l’esecuzione del programma è un’istruzione fondamentale nella maggior parte dei linguaggi di programmazione (i cosiddetti linguaggi imperativi). La parte di programma in cui un determinato nome è valido si chiama scope del nome. In molti linguaggi è possibile controllare lo scope di ogni nome, in modo che copra tutto il programma, una singola procedura o anche solo un blocco di istruzioni. Utilizzare lo scope più piccolo possibile diminuisce il rischio di errori e rende più chiaro il codice.
I programmi per computer possono manipolare una gran varietà di dati: immagini, suoni, sequenze di caratteri, numeri, e così via, ma tutti questi dati sono rappresentati in memoria come sequenze di 0 e di 1. La sequenza 01000001 può rappresentare, per esempio, il numero intero 65, la lettera A oppure un frammento di un’immagine in bianco e nero che contiene due pixel bianchi e sei neri. L’informazione che permette di sapere quale sia l’interpretazione giusta si chiama tipo del dato. Ogni linguaggio definisce alcuni tipi fondamentali e spesso permette al programmatore di aggiungere tipi nuovi. Fra i tipi fondamentali si trovano in genere vari tipi numerici, le sequenze di caratteri (chiamate stringhe) e il cosiddetto tipo booleano, che può assumere solo i valori vero e falso. Un altro tipo fondamentale particolarmente importante è il riferimento o puntatore: invece di rappresentare un dato, ne indica la posizione all’interno della memoria. Tale tipo di dato rappresenta la base per costruire strutture articolate, come i grafi o gli alberi, formate da entità collegate fra loro in vario modo.
I tipi elementari possono essere aggregati fra loro formando altri tipi. Le forme più comuni di aggregazione prevedono di identificare le singole componenti con un numero o una ennupla di numeri (formando i cosiddetti , cioè vettori o matrici) oppure con un nome (dando vita ai record, alle strutture, agli array associativi ecc.).
L’associazione fra tipi e dati è un’importante caratterizzazione del linguaggio. Nei linguaggi staticamente tipizzati (come C, C++, C#, Java, Delphi, ML, Haskell), essa non cambia durante l’esecuzione del programma. Il programmatore deve in genere dichiarare esplicitamente la variabile, specificandone il tipo, anche se in alcuni linguaggi (come ML o Haskell), il compilatore o l’interprete sono in grado di inferire il tipo corretto dal contesto. Assegnare alla variabile un valore di tipo diverso o utilizzarla in un contesto inappropriato per il suo tipo (per es., eseguire un’operazione aritmetica su una variabile di tipo non numerico) genera un errore in fase di compilazione. Proprio il fatto che il compilatore sia in grado di effettuare automaticamente queste verifiche viene considerato uno dei principali vantaggi di questo approccio. Nei linguaggi dinamicamente tipizzati (APL, Objective-C, Lisp, Smalltalk, JavaScript, Tcl, Prolog, Python, Ruby ecc.), il legame fra variabile e tipo può cambiare nel corso dell’esecuzione: la variabile, per così dire, contiene sia il dato che il tipo. Un utilizzo scorretto può essere segnalato solo durante l’esecuzione. I sostenitori di questo approccio sostengono che permetta una programmazione più semplice e rapida.
Un’altra differenza si può definire fra i linguaggi fortemente tipizzati (per es., Ada, Java, ML, Oberon) in cui i dati di tipo diverso non si possono mescolare, e i linguaggi debolmente tipizzati (come C, Assembly, C++, Tcl), nei quali possono essere effettuate delle conversioni (cast).
Ogni tipo prevede un insieme di operazioni fondamentali che possono essere effettuate sui dati corrispondenti. Per esempio, due numeri interi possono essere sommati, sottratti, confrontati e così via. Molti linguaggi definiscono una sintassi particolarmente comoda per esprimere queste operazioni. Vengono utilizzati segni (operatori) che possono essere combinati fra loro in modo da formare espressioni simili all’usuale notazione matematica. Se A, B, C sono tre variabili, è in genere possibile scrivere per esempio A*(B+C)/2 se si intende indicare la somma di B e C moltiplicata per A e divisa per due.
Possono esserci notevoli differenze fra un linguaggio e l’altro. Per esempio, può variare la scelta del carattere (o della sequenza di caratteri) usato per esprimere una determinata operazione: xy si scrive x^y in VisualBasic, x**y in Fortran, mentre in C/C++ non è disponibile come operatore e si deve quindi scrivere pow(x,y). Possono esserci differenze nella gestione delle precedenze: per esempio, l’espressione a+b*c può essere considerata equivalente a a+(b*c) oppure a (a+b)*c. In molti linguaggi il compilatore segue la convenzione usuale e dà la precedenza alla moltiplicazione. Alcuni linguaggi seguono più semplicemente l’ordine da sinistra a destra (per es., Occam), oppure da destra a sinistra (per es., APL). In altri casi (come il Forth o PostScript) si utilizza la cosiddetta notazione polacca inversa, in cui si scrivono prima gli argomenti e poi gli operatori: nel caso precedente si ha a b c * +.
La semantica dell’operazione a+b dipende in genere dal tipo dei dati coinvolti: può indicare l’addizione fra due numeri, la somma di due vettori, la concatenazione di due stringhe e così via (per es., in alcuni linguaggi si può scrivere 3+5 che vale 8 mentre ‘3’+‘5’ vale ‘35’). L’associazione di più significati a uno stesso simbolo è detta (letteralmente: sovraccarico). Alcuni linguaggi permettono di creare nuovi tipi e associare ai simboli convenzionali le relative operazioni. È possibile per esempio definire entità matematiche non presenti nei tipi base del linguaggio (tensori, quaternioni, numeri complessi ecc.) e fare in modo che si possa esprimere la moltiplicazione fra queste entità utilizzando lo stesso simbolo * che è associato alla normale moltiplicazione fra numeri.
La scelta di utilizzare caratteri speciali per indicare gli operatori pone un problema quando il numero di questi ultimi diventi grande. Un linguaggio come il C++, relativamente ricco di operatori, ne definisce molti utilizzando coppie di caratteri (per es., 〈 〈 o ! =). Un approccio diverso è seguito da APL, che sfrutta caratteri speciali che non si trovano sulle tastiere normali. I programmi scritti in APL sono straordinariamente compatti (benché possano apparire oscuri a chi non conosca bene il linguaggio).
Le istruzioni di controllo del flusso regolano l’ordine di esecuzione delle istruzioni di un programma e sono indispensabili per l’implementazione di ogni algoritmo non banale. L’istruzione fondamentale è il cosiddetto trasferimento condizionato del controllo. In dipendenza da una ben definita condizione (per es., se il valore di una certa variabile è maggiore di 0), l’esecuzione continua in un altro punto del codice, altrimenti prosegue normalmente con l’istruzione successiva. Utilizzando questo costrutto è possibile concatenare frammenti di codice formando delle strutture arbitrariamente complesse. Il paradigma della cosiddetta programmazione strutturata impone vincoli a queste strutture, con il fine di rendere i programmi più facili da comprendere, correggere e modificare. Sono permesse solo tre strutture di controllo: la sequenza, la selezione e l’iterazione, ognuna con un solo punto di ingresso e un solo punto di uscita. Esse possono essere combinate fra loro a seconda delle necessità.
La maggior parte dei linguaggi di programmazione ha una propria versione dei tre costrutti (oltre a consentire in genere altri costrutti meno ortodossi). La sequenza è semplicemente un blocco di codice eseguito dall’inizio alla fine. La selezione è costituita da uno o più blocchi dei quali ne venga eseguito al più uno in dipendenza di una precisa condizione. Esempi di questo costrutto sono le istruzioni if-then, if-then-else, switch, select, presenti in moltissimi linguaggi con minime variazioni sintattiche. L’iterazione, chiamata anche ciclo o loop, prevede un blocco di codice da eseguire un arbitrario numero di volte (al limite, anche zero). Esempi di iterazione sono for, foreach, while, do-while, repeat-until.
Alcuni costrutti che violano i principi della programmazione strutturata sono il goto e il throw-catch. Quest’ultimo permette di implementare la cosiddetta gestione delle eccezioni nei linguaggi che supportano questo costrutto (Ada, C++, Corn, Delphi, Objective-C, Java, Eiffel, Ocaml, Python, Common Lisp, SML, PHP e i linguaggi della piattaforma .NET). Il meccanismo delle eccezioni permette di trattare separatamente la gestione degli eventi straordinari (per es., gli errori) evitando di appesantire il codice principale con una miriade di controlli.
È pratica comune organizzare i programmi in più porzioni ognuna delle quali risolva un ben definito sottoproblema. I relativi costrutti prendono diversi nomi nei vari linguaggi e in dipendenza dalle loro caratteristiche: procedure, subroutine, sottoprogrammi, funzioni, metodi, messaggi. In ogni caso si tratta di blocchi di codice delimitati con precisione, in genere identificati con un nome e che si comportano come un piccolo programma in miniatura: elaborano dati e forniscono un risultato seguendo un determinato algoritmo. Sono definiti in un punto del programma e possono essere richiamati in altri punti. In corrispondenza della chiamata (detta anche invocazione o attivazione), l’esecuzione del programma salta al codice della procedura, lo esegue e poi riprende dall’istruzione successiva. La procedura può restituire un valore e può avere effetti collaterali, come modificare il valore di una variabile o effettuare delle operazioni di I/O (input/output). Molti linguaggi utilizzano il termine funzione quando viene restituito un valore, procedura negli altri casi. A volte (come in C o in Java) le procedure sono considerate un caso speciale di funzioni il cui valore di ritorno sia irrilevante.
Le procedure limitano la ridondanza del codice, ne migliorano la chiarezza e ne facilitano il riutilizzo: una procedura dalle competenze ben definite, definita nell’ambito di un programma, può servire anche in un altro programma. Questa pratica è molto diffusa. Le procedure il cui riutilizzo appare più probabile vengono in genere raccolte in librerie (termine derivato da un’errata traduzione del termine inglese library, che letteralmente significa biblioteca). Accanto alle librerie create man mano dal programmatore ci possono essere librerie di sistema, spesso parte integrante del linguaggio, di cui ne estendono le funzionalità.
Una procedura può richiamare altre procedure (può in genere anche richiamare se stessa: procedimento versatile e potente, noto come ).
Lo scambio di dati fra la procedura chiamata e quella chiamante può avvenire con diverse modalità: mediante variabili condivise (ovvero il cui scope comprenda chiamato e chiamante), oppure, in alcuni linguaggi (come Forth o PostScript) utilizzando lo (pila), un’area di memoria in cui i dati possono essere accumulati e rimossi sequenzialmente. Il sistema più diffuso prevede l’uso di parametri, speciali variabili associate alla procedura e dedicate a questo scopo. L’utilizzo dei parametri è molto articolato e può essere assai differente nei vari linguaggi. I parametri possono essere usati per trasferire dati dal chiamante al chiamato o viceversa; una procedura può essere chiamata con un numero variabile di parametri; si possono definire procedure diverse con lo stesso nome, distinte solo per il numero ed eventualmente il tipo dei parametri; nell’invocazione l’associazione fra parametri e loro valore può essere fatta in base alla posizione, ma anche al nome, e così via.
In alcuni linguaggi (tra cui Lisp, Python, JavaScript, Matlab), le funzioni sono trattate come un tipo di dato: possono essere create, assegnate a una variabile o passate come argomento a un’altra procedura. In questi linguaggi è possibile anche creare funzioni anonime, cioè non associate ad alcun nome.
Le procedure sono casi particolari di un costrutto più generale: le coroutine (definite, tra gli altri, in Simula, Modula-2, C#, Lua, e utilizzabili anche nei linguaggi assemblativi). Le coroutine possono, mediante un’apposita istruzione, restituire il controllo alla procedura chiamante e, alla successiva riattivazione, riprendere l’esecuzione dall’istruzione successiva. Sono un costrutto relativamente poco diffuso, ma assai versatile e potente.
La cosiddetta programmazione orientata agli oggetti (OOP, Object-oriented programming) rappresenta uno dei paradigmi di programmazione più noti e utilizzati, e molti linguaggi hanno costrutti specificamente dedicati a questo paradigma. Un oggetto è un’entità che raggruppa un insieme di dati (attributi o membri) e le operazioni che è possibile effettuare sui dati stessi (metodi o messaggi). Il programmatore progetta in genere gli oggetti in modo che rappresentino entità del mondo reale (un oggetto può, per es., rappresentare una nave, con la sua posizione geografica, la sua velocità o il peso del suo carico e le operazioni relative possono essere la modifica della velocità, l’aggiornamento della posizione in base alla velocità, ecc.). Il paradigma della programmazione orientata agli oggetti si articola attorno a tre concetti chiave: l’incapsulamento, il polimorfismo e l’ereditarietà. L’incapsulamento consiste nella separazione fra l’utilizzo di un oggetto e la conoscenza dei dettagli del suo funzionamento. Il polimorfismo permette a oggetti diversi di eseguire in modo differente la stessa operazione: per esempio, oggetti che rappresentino differenti figure geometriche sono in grado di disegnarsi sullo schermo ognuno in maniera diversa. Non è insolito che un’operazione coinvolga più di un oggetto e può essere utile definire un comportamento polimorfo che tenga conto del tipo di tutti gli oggetti coinvolti (per es., il calcolo dell’intersezione di due figure geometriche dipende ovviamente da entrambe le forme coinvolte). Alcuni linguaggi (CLOS, Dylan, Nice, ecc.) prevedono un costrutto specifico () per gestire questi casi.
In molti linguaggi orientati agli oggetti viene definito il concetto di classe che rappresenta una sorta di stampo per creare gli oggetti. L’ereditarietà è la capacità di definire una classe come specializzazione di una classe più generale (nell’esempio delle figure geometriche possiamo pensare ad una classe Figura da cui discendono le classi Cerchio e Quadrato).
La programmazione orientata agli oggetti è efficace quando il programma può essere diviso in elementi con una ben definita e relativamente limitata interazione reciproca e quando gli algoritmi implementati siano intimamente legati alle relative strutture dati. Altri paradigmi di programmazione possono essere più appropriati in altri casi. Per esempio, la programmazione orientata agli aspetti (AOP, Aspect-oriented programming: ne sono esempi AspectJ e AspectC++, tra gli altri) gestisce meglio le funzionalità che interessano contemporaneamente tanti oggetti diversi, come il monitoraggio delle operazioni svolte (logging) mentre la programmazione generica (C++, D, Beta, Eiffel, Clu, Ada, ML, Java 5.0 ecc.) permette di definire degli algoritmi applicabili a differenti strutture dati non correlate fra loro.
3. Migliaia di linguaggi
Con una salda base teorica e un’ampia disponibilità di strumenti tecnici, oggi inventare e implementare un linguaggio di programmazione è relativamente semplice: un nuovo linguaggio può nascere nell’ambito di una tesi di dottorato, o addirittura di un corso universitario, o magari solo per gioco (si pensi, per es., a Intercal, BrainFuck, Whitespace). Anche considerando solo i linguaggi la cui definizione viene resa pubblica, rimane difficile stimarne il numero complessivo. Sicuramente dai primi anni Cinquanta fino all’inizio del nuovo millennio sono stati ideati (e molto spesso implementati) diverse migliaia di linguaggi. Tra questi si trovano linguaggi completamente diversi come Cobol e Haskell, differenti versioni dello stesso linguaggio come Fortran IV e Fortran90, dialetti come Scheme e Common LISP (che condividono un impianto generale, ma presentano differenze marcate) e infine diverse implementazioni dello stesso linguaggio non perfettamente compatibili, come Borland Pascal e GNU Pascal. Nella babele dei linguaggi di programmazione il nome del linguaggio può essere una guida poco significativa: SETL2 ha attraversato sostanziali modifiche senza cambiare nome mentre IAL, ALGOL e ALGOL 58 sono tre nomi diversi, assunti successivamente dall’identico linguaggio. Può succedere anche che lo stesso nome venga usato per più di un linguaggio, come nel caso di Vulcan che corrisponde a tre linguaggi diversi.
I linguaggi più popolari possono ricevere l’attenzione di un organismo di standardizzazione come ISO, IEC o ANSI e, dopo un processo che dura qualche anno, ricevere una definizione standard. Lo standard stabilizza il linguaggio, ma in genere non ne blocca l’evoluzione, che continua, seppure a un ritmo più lento e controllato.
I linguaggi hanno un ciclo di vita: nascono, cambiano, invecchiano e a volte muoiono. Alcuni, come il Fortran, si sono dimostrati straordinariamente longevi (mutando in maniera sensibile la loro struttura: il Fortran I e il Fortran 2004 sono linguaggi essenzialmente diversi). Altri sono stati popolari per un tempo relativamente breve. Di un numero sorprendentemente piccolo di linguaggi si può certificare la morte ufficiale. Lo sviluppo di un nuovo linguaggio è spesso profondamente influenzato dai linguaggi preesistenti e l’insieme costituisce un intrecciato albero genealogico.
Nonostante gli evidenti svantaggi creati da questa stupefacente proliferazione, tutti i tentativi di progettare e imporre un linguaggio universale sono falliti. È impossibile concentrare in un solo linguaggio tutte le caratteristiche desiderabili, in quanto esse possono essere in contrasto fra loro e dipendono fortemente dal contesto: tipo di problema affrontato, requisiti di efficienza necessari, computer utilizzati, numero dei programmatori coinvolti e loro esperienza. Di fatto diversi fattori contribuiscono a mantenere alto il numero di linguaggi utilizzati. Linguaggi specializzati possono essere necessari in particolari casi (per es., per gestire computer non convenzionali o per affrontare determinati problemi). Lo sviluppo di nuovi paradigmi di programmazione porta inevitabilmente all’introduzione di nuovi linguaggi che ne facilitino l’adozione. Nel contempo è difficile abbandonare un linguaggio diventato popolare e che vanti molti programmi installati (che richiedono manutenzione) e programmatori esperti (che possono non volere imparare un linguaggio nuovo). Nei primi anni del millennio la comunità dei programmatori (stiamo parlando probabilmente di diversi milioni di persone), utilizza in maniera significativa alcune decine di linguaggi di programmazione. È molto difficile valutare quali siano i linguaggi effettivamente più usati nei vari contesti (insegnamento, ricerca, produzione). È probabile che i 10 linguaggi più popolari si trovino in questa lista (ordinata alfabeticamente): Ada, ASP, C, C++, C#, Cobol, ColdFusion, Delphi/Kylix, Fortran, Eiffel, Java, JavaScript, Lisp, Pascal, Perl, PHP, Python, SAS, SQL, Visual Basic, Visual Fox Pro.
I linguaggi di programmazione si possono valutare secondo vari criteri: espressività, semplicità, leggibilità, efficienza, affidabilità, , popolarità.
L’espressività è la capacità di esprimere in maniera concisa operazioni complicate. Si può avere una misura dell’espressività calcolando a quante istruzioni in linguaggio macchina corrisponda in media un’istruzione del linguaggio considerato. La semplicità condiziona il tempo necessario per apprendere e padroneggiare un linguaggio: i linguaggi semplici sono fondamentali per la didattica. La leggibilità è la facilità di comprendere finalità e funzionamento di un programma esaminando il codice sorgente. È un fattore determinante nella manutenzione del programma in quanto facilita correzione degli errori e modifiche funzionali. L’efficienza misura la qualità (in termini di dimensioni o di velocità di esecuzione) dell’eseguibile prodotto. L’affidabilità si basa sull’assenza di costrutti per così dire pericolosi (tali cioè che il loro utilizzo comporti un elevato rischio di commettere degli errori di programmazione) e sulla presenza di controlli, effettuati dal compilatore o dal programma stesso durante l’esecuzione, che permettano di individuare il prima possibile eventuali errori. La portabilità indica la possibilità di adattare un programma su una diversa piattaforma (cpu + architettura + sistema operativo) con un numero limitato o preferibilmente nullo di modifiche al codice sorgente. La popolarità di un linguaggio, ovvero la sua diffusione, condiziona disponibilità di manuali, corsi di formazione, programmatori esperti, efficienti ambienti di sviluppo, librerie di funzioni predefinite, e così via.
4. Tassonomia dei linguaggi di programmazione
Di fronte a una simile quantità di linguaggi diventa naturale cercare di organizzarli in classi. Di fatto ci sono tanti criteri per ordinare i linguaggi e la classificazione non può essere molto rigida visto che spesso i criteri sono ortogonali fra loro: CLOS, per esempio, è un linguaggio funzionale, ma anche object oriented. Esaminiamo alcuni criteri di classificazione più diffusi.
Abbiamo già visto la distinzione fra linguaggi di basso livello (linguaggio macchina o linguaggi assemblativi) e linguaggi di alto livello, più vicini al dominio del problema e che permettono di utilizzare direttamente costrutti, formule e notazioni simili a quelli utilizzati in quel dominio. I linguaggi ad alto livello comprendono la maggior parte di quelli utilizzati attualmente, in uno spettro continuo che vede il C e il Forth a un livello più basso rispetto a Java o all’Apl. I linguaggi di script e alcuni linguaggi molto specializzati possono essere considerati linguaggi ad altissimo livello.
Una classificazione parallela alla precedente divide i linguaggi in cinque generazioni. Le prime due generazioni sono rispettivamente i linguaggi macchina e i linguaggi assemblativi, mentre la terza generazione comprende linguaggi di alto livello come il C o Java (anche se a volte si parla di linguaggi di seconda generazione o 2GL per indicare i primi linguaggi di alto livello, come il Fortran). I linguaggi di quarta generazione o 4GL (sviluppati a partire dagli anni Ottanta) hanno lo scopo di aumentare la produttività nell’ambito di un problema specifico e di permettere l’uso del linguaggio anche a non programmatori. Spesso utilizzano una sintassi simile all’inglese. Un esempio è SQL (Structured query language) per l’interrogazione dei database oppure Mathematica per la manipolazione di espressioni simboliche. I linguaggi di quinta generazione (5GL) applicano alla programmazione tecniche di intelligenza artificiale (sistemi esperti, regole di inferenza, riconoscimento del linguaggio naturale). In questi linguaggi, invece di descrivere dettagliatamente l’algoritmo di soluzione, si descrive il problema, fornendo un insieme di vincoli e lasciando al sistema la generazione di tutte le soluzioni con essi compatibili. La maggior parte dei linguaggi logici sono considerati linguaggi di quinta generazione.
È possibile classificare i linguaggi secondo il modello di calcolo utilizzato: esistono linguaggi imperativi (la maggior parte di quelli oggi utilizzati), funzionali e logici. Gli ultimi due sono spesso raggruppati nella classe dei linguaggi dichiarativi.
Nei linguaggi imperativi il programma è costituito da comandi che modificano lo stato del sistema (per es., cambiando il valore di una variabile o causando la scrittura di caratteri da parte del dispositivo di output).
Un programma scritto in un linguaggio funzionale contiene la definizione di un certo numero di funzioni. L’esecuzione del programma consiste nel calcolo del valore di una particolare funzione, per determinati valori dei suoi argomenti. La funzione in genere richiederà a sua volta il calcolo di altre funzioni e così via. I linguaggi funzionali puri non prevedono variabili il cui valore possa essere modificato. Una funzione invocata con gli stessi argomenti restituisce sempre lo stesso valore, e non può avere effetti collaterali. Questa caratteristica risulta preziosa nella programmazione dei computer paralleli, quando l’ordine di invocazione delle varie funzioni può essere indeterminato. Anche le dimostrazioni formali di correttezza dei programmi sono semplificate dall’assenza di effetti collaterali. Nella pratica il concetto di variabile è così comodo che spesso viene introdotto sacrificando la purezza del linguaggio. La differenza fra un linguaggio funzionale non puro (che permette di modificare le variabili) e un linguaggio imperativo (che in genere permette la definizione di funzioni) è relativamente ridotta. Linguaggi funzionali puri sono Clean, FP, Haskell, Hope, Joy, LML, Miranda. Linguaggi funzionali non puri sono Lisp e Scheme. I fogli elettronici (Excel, Star Office Calc ecc.) possono essere considerati linguaggi funzionali.
Infine esistono i linguaggi logici che appartengono, assieme ai linguaggi funzionali puri, alla classe dei linguaggi dichiarativi. I linguaggi logici permettono al programmatore di definire un insieme di fatti e di regole e di formulare domande. Il sistema risponde alle domande sulla base dei dati ricevuti. Può controllare se un fatto è vero, cioè se discende logicamente dalle informazioni fornite, e può generare tutte le combinazioni di parametri che rendono vera una frase. Linguaggi logici sono, fra gli altri, Prolog e Mercury. Come per i linguaggi funzionali, anche i linguaggi dichiarativi spesso incorporano qualche elemento procedurale.
Un altro criterio di classificazione molto usato fa riferimento ai paradigmi di programmazione. Parleremo quindi di linguaggi strutturati, orientati agli oggetti, orientati all’aspetto e così via, se nel linguaggio sono presenti costrutti che facilitino la programmazione secondo il paradigma corrispondente ed eventualmente scoraggino o proibiscano gli approcci antagonisti (per es., un linguaggio strutturato che non disponga dell’istruzione goto).
È anche possibile caratterizzare i linguaggi secondo il contesto di utilizzo: possiamo distinguere fra linguaggi di utilizzo generale (come Ada) e linguaggi limitati a un dominio specifico (come Yacc che serve per scrivere compilatori, PostScript per la descrizione tipografica di pagine di testo o PovRay per la generazione di scene 3D), possiamo parlare di linguaggi di script oppure di linguaggi concorrenti (studiati per gestire l’esecuzione simultanea di parti diverse del programma), e via di questo passo.
5. Un tocco di stile
L’interprete o il compilatore distruggono informazione: il codice ad alto livello è più ricco del linguaggio macchina che viene eseguito. La scelta dei nomi delle variabili e delle procedure, la scrittura di commenti, la scelta fra costrutti sintattici equivalenti o la disposizione tipografica del codice non hanno effetto sul programma eseguibile, ma ne modificano la leggibilità. Quest’ultima caratteristica è eminentemente soggettiva e non stupisce che, attraverso i costrutti elencati prima, i programmatori esprimano un loro personale stile.
Un momento cruciale nella programmazione è la scelta dei nomi per le varie entità che costituiscono il programma (variabili, funzioni, procedure, classi ecc.). I nomi devono rispettare alcune regole di composizione (spesso il primo carattere non può essere una cifra e non sono consentiti segni di interpunzione, ci può essere una limitazione sul numero massimo di caratteri, talvolta i caratteri iniziali o finali sono significativi e indicano alcune caratteristiche dell’entità nominata, come il tipo di una variabile, ecc.), ma entro queste regole la scelta rimane ampia. Scegliere buoni nomi, né troppo lunghi né troppo corti, appropriati e facili da ricordare ha una grande influenza sulla leggibilità del codice.
Nella composizione dei nomi si seguono spesso delle convenzioni che governano l’uso delle maiuscole, delle minuscole e dei segni non alfabetici (scegliendo per es. fra raggio_del_cerchio e raggioDelCerchio). Relativamente diffusa (specialmente nei linguaggi debolmente tipizzati) è la cosiddetta notazione ungherese, la quale prevede per i nomi delle variabili l’uso di un sistema di prefissi significativi e nella quale, per esempio, m_nNumeroLati indica un membro di una classe (m_) di tipo intero (n).
I commenti sono un costrutto studiato appositamente per migliorare la leggibilità: vengono ignorati dal compilatore o dall’interprete e permettono di aggiungere spiegazioni e documentazione, di includere informazioni come data, autore, copyright, e così via, e possono essere anche usati per separare visivamente blocchi di codice mediante cornici. Esistono degli strumenti (per es., javadoc per Java o doxygen per C++, C, e altri) che estraggono le informazioni inserite sotto forma di commenti preceduti da un particolare prefisso, le confrontano con il resto del codice e producono automaticamente i file di documentazione. Quest’idea viene spinta ancora oltre nel paradigma del cosiddetto literate programming, inventato da Donald Knuth nel 1984. Questo paradigma rovescia il rapporto fra commenti e codice: quest’ultimo è inserito all’interno del documento che lo descrive e ne viene estratto automaticamente solo durante la compilazione.
I cosiddetti linguaggi a formato libero permettono di distribuire il testo del programma andando a capo quando lo si desidera e aggiungendo spazi a piacere. In questi linguaggi la formattazione viene usata per migliorare la leggibilità del codice, per esempio aggiungendo spazi all’inizio della riga (indentazione) in modo da evidenziare tipograficamente la struttura a blocchi annidati del programma. La maggior parte degli ambienti di sviluppo (IDE) indentano il codice automaticamente. Alcuni linguaggi di programmazione (fra cui Occam, Haskell, Python) rendono l’indentazione significativa e quindi obbligatoria. Questi linguaggi non hanno parole chiave o segni per delimitare i blocchi di codice, ma utilizzano la sola indentazione.
La programmazione è un’attività creativa che presuppone anche un aspetto ludico. La libertà nella disposizione del testo è stata tra l’altro utilizzata per ricreare l’equivalente informatico dei calligrammi.
Bergin, Gibson 1996: History of programming languages II, edited by Thomas J. Bergin, Richard G. Gibson, New York, ACM Press, 1996.
Burnett, Baker 1994: Burnett, Margaret M. - Baker, Marla J., A classification system for visual programming languages, “Journal of visual languages and computing”, 5, 1994, pp. 287-300.
Burnett 1999: Burnett, Margaret, Visual programming, in: Encyclopedia of electrical and electronics engineering, edited by John G. Webster, New York, Wiley, 1999.
Cezzar 1995: Cezzar, Ruknet 1995: A guide to programming languages: overview and comparison, Boston, Artech House, 1995.
Chen 2005: Chen, Yaofei e altri, An empirical study of programming language trends, “IEEE software”, 22, 2005, pp. 72-78.
Chomsky 1956: Chomsky, Noam, Three models for the description of language, “IEEE transactions on information theory”, 2, 1956, pp. 113-124.
Dahl 1972: Dahl, Ole-Johan - Dijkstra, Edsger W. - Hoare, Charles A.R., Structured programming, London-New York, Academic Press, 1972.
Dijkstra 1968: Dijkstra, Edsger W., Go to statement considered harmful, “Communications of the ACM”, 11, 1968, pp. 147-148.
Dijkstra 1970: Dijkstra, Edsger W., Notes on structured programming, 2. ed., Eindhoven, Technische Hogeschool Eindhoven, 1970.
Friedman 2001: Friedman, Daniel P. - Wand, Mitchell - Haynes, Christopher T., Essentials of programming languages, 2. ed., Cambridge (Mass.), MIT Press, 2001.
Gamma 1995: Gamma, Erich e altri, Design patterns: elements of reusable object-oriented software, Reading, (Mass.), Addison-Wesley, 1995.
Ghezzi, Jazayeri 1998: Ghezzi, Carlo - Jazayeri, Mehdi, Programming language concepts, 3. ed., New York, Wiley, 1998 (trad. it.: Concetti dei linguaggi di programmazione, Milano, Angeli, 1989).
Gunter 1992: Gunter, Carl A., Semantics of programming languages: structures and techniques, Cambridge (Mass.), MIT Press, 1992.
Knuth 2005: Knuth, Donald E., Art of computer programming, Upper Saddle River (N.J.), Addison-Wesley, 2005.
Louden 2003: Louden, Kenneth C., Programming languages: principles and practice, 2. ed., Pacific Grove (Calif.), Brooks/Cole, 2003.
McConnell 2004: McConnell, Steve, Code complete: a practical handbook of software construction, 2. ed., Washington D.C., Microsoft Press, 2004 (trad. it.: Ingegneria del codice: manuale pratico per la costruzione di software completo, Segrate, Mondadori Informatica, 2005).
Pierce 2005: Advanced topics in types and programming languages, edited by Benjamin C. Pierce, Cambridge (Mass.), MIT Press, 2005.
Pratt, Zelkowitz 2001: Pratt, Terrence W. - Zelkowitz, Marvin V., Programming languages: design and implementation, 4. ed., Upper Saddle River (N.J.), Prentice-Hall, 2001 (trad. it. della 2. ed.: Linguaggi di programmazione, Milano, Jackson, 1988).
Sammet 1969: Sammet, Jean E., Programming languages: history and fundamentals, Englewood Cliffs (N.J.), Prentice-Hall, 1969.
Sebesta 2002: Sebesta, Robert W., Concepts of programming languages, 5. ed., Boston, Addison-Wesley, 2002.
Slonneger, Kurtz 1995: Slonneger, Kenneth - Kurtz, Barry L., Formal syntax and semantics of programming languages: a laboratory based approach, Reading (Mass.), Addison-Wesley, 1995.
Stroustrup 2000: Stroustrup, Bjarne, The C++ programming language, 3. ed., Reading (Mass.), Addison-Wesley, 2000 (trad. it.: C++. Linguaggio, libreria standard, principi di programmazione, 3. ed., Milano, Addison-Wesley Longman Italia, 2000).
Tesler 1984: Tesler, Lawrence G., Programming languages, “Scientific American”, 251, 1984, pp. 70-78.
Van Roy, Haridi 2004: van Roy, Peter - Haridi, Seif, Concepts, techniques and models of computer programming, Cambridge (Mass.), MIT Press, 2004.
Watt, Muffy 1991: Watt, David A. - Muffy, Thomas, Programming languages: syntax and semantics, New York, Prentice-Hall, 1991.
Watt, Findlay 2004: Watt, David A. - Findlay, William, Programming language design concepts, Hoboken (N.J.), Wiley, 2004.
Wexelblat 1981: History of programming languages, edited by Richard L. Wexelblat, New York, Academic Press, 1981.
Wilson, Clark 2000: Wilson, Leslie B. - Clark, Robert G., Comparative programming languages, 3. ed., edited by Robert G. Clark, Reading (Mass.), Addison-Wesley, 2000.
Il mondo dei linguaggi di programmazione è in continua evoluzione. Può risultare utile ricorrere al Web per averne una visione sempre aggiornata. I siti di seguito indicati rappresentano una traccia ragionata per poterne seguire le principali tappe in tempo reale.
Armbruster, Fred, e altri, The history of programming languages,
http://www.an.psu.edu/ojj/courses/ist-240/reports/spring2001/fa-cb-bc-kf/historyindex.html
Computer HopeTM, Computer history,
http://www.computerhope.com/history/
Curt Noll, Landon e altri, The international obfuscated C code contest,
Dahl, Ole-Johan - Nygaard, Kristen, How object-oriented programming started,
http://heim.ifi.uio.no/~kristen/FORSKNINGSDOK_MAPPE/F_OO_start.html
Diagram & history of programming languages,
http://merd.sourceforge.net/pixel/language-study/diagram.html
Findy Services - Jacobs, Bryce, How to design a programminglanguage,
http://www.geocities.com/tablizer/langopts.htm
Grygus, Andrew, Computer time line,
http://www.aaxnet.com/info/hist.html
Kinnersley, Bill, The language list,
http://people.ku.edu/~nkinners/LangList/Extras/langlist.htm
Knutsen, Steinar, Alphabetical list of programming languages,
http://sk.nvg.org/lang/index.html
Lebherz, Eric, Computing languages list,
http://www.hypernews.org/HyperNews/get/computing/lang-list.html
Lévénez, Éric, Computer languages history,
Menabrea, Luigi F., Sketch of the analytical engine invented by Charles Babbage (traduzione inglese e commento di Ada Augusta, contessa di Lovelace, pagina web di John Walker)
http://www.fourmilab.ch/babbage/sketch.html
Neumann, Michael, 433 examples in 132 (or 162*) programming languages,
http://www.ntecs.de/old-hp/uu9r/lang/html/lang.en.html
O’Reilly, The history of programming languages,
http://www.oreilly.com/pub/a/oreilly/news/languageposter_0504.html
Ousterhout, John K., Scripting: higher level programming for the 21st century,
http://www.tcl.tk/doc/scripting.html
Pigott, Diarmuid, HOPL: an interactive roster of programminglanguages,
Reiner, Jeremy, 30 years of personal computer market share figures,
http://arstechnica.com/articles/culture/total-share.ars/
Robat, Cornelis, History of computing industrial era,
http://www.thocp.net/timeline/timeline.htm
Schade, Oliver - Scheithauer, Gregor - Scheler, Stefan, 99 bottles of beer, one program in 881 variations,
http://www.99-bottles-of-beer.net/
Sureau, Denis G., History and evolution of computer languages,
http://www.scriptol.org/history.php
Sureau, Denis G., List of hello world programs in 200 programming languages,
http://www.scriptol.org/hello-world-programming-language.php
TIOBE Programming Community Index,
University of Michigan-Dearborn, The language guide,
http://www.engin.umd.umich.edu/CIS/course.des/cis400/
VerBeek, Todd, Programming languages,
http://microsoft.toddverbeek.com/lang.html
Wikipedia contributors, Programming language, Wikipedia, The Free Encyclopedia,
http://en.wikipedia.org/w/index.php?title=Programming_language&oldid=35983843
Ziring, Neal, Dictionary of programming languages,
http://users.erols.com/ziring/dopl.html