Ada, Algol, APL, Basic, C, Java, Cobol, Forth, Fortran, Lisp, Logo, Modula 2,
Pascal,
PL/I, Prolog, Smalltalk, Snobol,... Qual'è il linguaggio di programmazione migliore?
Perché esistono tanti linguaggi? Non ne bastava uno solo? Queste sono tipiche domande che
si pone chi si avvicina per la prima volta al mondo della programmazione. Al giorno d'oggi
esistono centinaia, forse migliaia, di linguaggi di programmazione diversi e, anche se
quelli veramente diffusi sono al più qualche decina (tutti gli altri vengono utilizzati
solo in una ristretta cerchia di fans, come i linguaggi creati in centri di ricerca,
linguaggi orientati ad applicazioni particolari, ecc.), sono più che abbastanza per
confondere le idee al povero neofita. Sono tutti utili?
Da un punto di vista teorico si è dimostrato che tutti i linguaggi non banali sono, dal
punto di vista della capacità computazionale, equivalenti. In pratica un
programma scritto in un certo linguaggio può sempre essere codificato in qualsiasi altro
linguaggio (ovviamente la valutazione della capacità computazionale non tiene conto dei
molteplici accessori, spesso dipendenti dall'hardware, presenti nelle varie implementazioni
dei linguaggi, come le istruzioni grafiche, i comandi per l'accesso agli
interrupt, ecc.).
Allora perché questa variopinta molteplicità di linguaggi? Il fatto è che esistono
molti altri fattori da prendere in considerazione, oltre alla pura teorica possibilità di
poter fare qualsiasi cosa con qualsiasi linguaggio. Il linguaggio deve essere non troppo
difficile (è il massimo che si può sperare!) da imparare, deve essere possibile creare
applicazioni in tempi ragionevoli, deve produrre eseguibili efficienti, ecc. Allora
perché non creare un unico linguaggio che ottimizzi tutti questi fattori? Perché non
creare un linguaggio universale? Malgrado vi siano stati sforzi in tal senso (ad esempio
il PL/I) i risultati sono stati tutt'altro che soddisfacenti. In realtà è anche vero che
alcuni linguaggi, soprattutto i più anziani, sono utilizzati solo per forza di inerzia,
caso esemplare è, tanto per non fare nomi, il Fortran che è stato uno dei primi
linguaggi di programmazione ed è ancora oggi diffusamente utilizzato per i calcoli
matematici sia per l'enorme quantità di librerie già esistenti sia per l'attaccamento,
quasi affettivo, dei suoi utilizzatori. D'altra parte anche i linguaggi naturali
(italiano, inglese, ecc.), proprio per la stessa forza di inerzia, non vengono sostituiti
tutti in blocco da un linguaggio molto più razionalizzato come potrebbe essere (ad
esempio) l'esperanto, malgrado gli enormi vantaggi che ciò comporterebbe. Nel caso dei
linguaggi di programmazione tale forza è, ovviamente, di gran lunga inferiore, però vi
sono altri motivi, più oggettivi, per la sopravvivenza di questa Torre di Babele.
Il fatto è che ogni linguaggio risulta più o meno adatto a secondo del tipo di
applicazione. Ad esempio il Lisp che è, molto meno efficiente del C, permette con estrema
facilità di effettuare manipolazioni simboliche, per cui viene molto usato nel campo
dell'Intelligenza Artificiale, soprattutto nella ricerca; il Basic permette di realizzare
rapidamente piccoli programmi, mentre è del tutto inadeguato per applicazioni di una
certa consistenza (almeno la versione originale, oggi ci sono in giro molti Basic Pascal
like, come il Visual Basic, che, avendo adottato molte caratteristiche del
Pascal,
presentano una maggiore flessibilità). Quindi un linguaggio di programmazione va scelto a
secondo dell'area applicativa (scientifico, gestionale, elaborazione testi, simulazioni,
ecc.) e delle caratteristiche del problema da risolvere. Come diceva Carlo V Parlo
spagnolo a Dio, italiano alle donne, francese agli uomini e tedesco al mio cavallo.
Quindi è importante comprendere bene quali sono le differenze tra i vari linguaggi, non
tanto rispetto alle differenze di notazione o di terminologia, quanto rispetto alla filosofia
e al modello computazionale che li contraddistingue e che ne determina la personalità.
Nel seguito, dopo una breve introduzione generale effettueremo una classificazione di
massima dei linguaggi di programmazione mentre in un prossimo articolo cercheremo di
individuare quali sono le caratteristiche da tenere presenti per poter confrontare e
quindi valutare un linguaggio di programmazione rispetto alle proprie necessità.
I linguaggi formali
Per chiarirci le idee vediamo brevemente quali sono le differenze tra i linguaggi
formali e quelli naturali.
Quest'ultimi sono quelli che utilizziamo normalmente per comunicare tra noi esseri umani
("naturali" nel senso che non sono nati a tavolino, ma spontaneamente).
Essi non sono rigorosamente definiti, sono in continua evoluzione e spesso presentano
delle ambiguità; hanno però una enorme potenza espressiva. I linguaggi formali, invece,
sono completamenti definiti mediante regole esplicite, per cui è sempre possibile
determinare la correttezza (grammaticale) di una proposizione; inoltre il significato di
ogni frase è sempre privo di ambiguità. Però hanno un potere espressivo limitato.
I linguaggi di programmazione sono un sottoinsieme di quelli formali. Essi possono essere
definiti come il mezzo che ci permette di comunicare al computer la sequenza di
operazione da effettuare per raggiungere un obiettivo prefissato.
Oltre ai linguaggi di programmazione vi sono anche altri linguaggi formali. Un esempio è
la notazione scacchistica utilizzata per descrivere con precisione e senza
ambiguità le partite del diffuso gioco di strategia. Un'altra disciplina in cui si fa
largo uso dei linguaggi formali è la Logica Matematica, in cui vengono utilizzati per
descrivere le teorie matematiche e i processi deduttivi in modo rigoroso. Si noti, per
inciso, che i legami tra la Logica Matematica e l'Informatica stanno diventando sempre
più stretti con una forte influenza reciproca. Tra l'altro due linguaggi popolari, il
Lisp e il Prolog, sono scaturiti proprio da teorie studiate in Logica Matematica, e
precisamente dal lambda calcolo di Church e dal calcolo dei predicati del
primo ordine.
Linguaggi di basso ed alto livello
Non è possibile effettuare una rigida classificazione dei linguaggi di programmazione,
ma nel seguito tenteremo comunque di individuare alcune categorie generali in cui si
possono suddividere.
Una prima distinzione possiamo farla tra i linguaggi a basso e quelli ad alto
livello. Ogni processore ha un proprio linguaggio che ad ogni stringa di bit fa
corrispondere una operazione elementare come il caricamento di un registro interno al
processore o la somma tra una cella di memoria e un registro. Questo tipo di linguaggio,
detto linguaggio macchina, essendo molto vicino alla logica del processore,
risulta essere molto lontano dal modo di ragionare dell'uomo, per cui utilizzarlo per la
codifica di algoritmi comporta un lavoro molto lungo e difficile. Agli albori
dell'informatica questo era l'unico modo di programmare un computer, per cui tale
attività era riservata solo a tecnici super specializzati. Per alleviare queste
difficoltà si pensò di creare dei linguaggi intermedi con cui scrivere i programmi. Un
algoritmo codificato in questo modo non è più direttamente eseguibile dal processore, ma
è necessario utilizzare un apposito programma traduttore che converte il programma
originale (detto file sorgente) nelle corrispondenti istruzioni in linguaggio
macchina (ottenendo così il file oggetto). Il primo di tali linguaggi fu il linguaggio
Assembler, in cui al posto di ogni istruzione macchina viene usato un codice
mnemonico ad esso associato. L'Assembler, pur permettendo una semplificazione del lavoro,
costringe ancora a ragionare in un modo strettamente legato a quello del processore. Per
cui in seguito si cercò di distaccarsi sempre più dalla logica dei processori arrivando
così ai cosiddetti linguaggi ad alto livello orientati non più alla macchina ma alla
soluzione di problemi.
Un'altro importante vantaggio dei linguaggi ad alto livello (e, storicamente, un'altra
spinta al loro sviluppo) è il fatto di essere virtualmente indipendenti dal processore e
dalla macchina particolare su cui si sviluppa. In questo modo è possibile utilizzare lo
stesso sorgente su macchine diverse, ovvero come si dice in gergo informatico si ha una
maggiore portabilità delle applicazioni (e dei programmatori che non sono
costretti a imparare un nuovo linguaggio ogni volta che devono lavorare su una macchina
diversa).
Al giorno d'oggi l'Assembler viene utilizzato solo in casi particolari: o quando è
necessario molta efficienza (in quanto anche se i traduttori cercano di ottimizzare il
codice macchina risultante, lavorare direttamente in Assembler di solito permette di avere
un codice migliore) oppure quando si deve operare a livello macchina (ad esempio per
interfacciarsi a delle schede hardware). Per cui normalmente un'applicazione viene scritta
in gran parte in un linguaggio ad alto livello, mentre solo le parti più delicate
vengono, eventualmente, codificate in Assembler.
Le categorie
La letteratura informatica normalmente suddivide i linguaggi ad alto livello in quattro
categorie (imperativi, funzionali, dichiarativi ed orientati
ad oggetto). Noi utilizzeremo una classificazione più ampia esaminando anche alcune
categorie meno consuete. Sottolineiamo comunque che le classi considerate non sono
mutuamente esclusive, per cui un linguaggio può appartenere anche a più di una
categoria. Analizziamo ora le loro caratteristiche.
Imperativi: il programma è costituito da una
sequenza di istruzioni il cui effetto è quello di modificare il contenuto della memoria
dell'elaboratore o di determinare le modalità di esecuzione di altre istruzioni; in
questo modello assume un ruolo fondamentale l'istruzione di assegnazione. Sono
imperativi la maggior parte dei linguaggi più diffusi (Pascal, Basic, Fortran, C, Cobol,
ecc.).
Funzionali: il programma è considerato come il
calcolo del valore di una funzione; in un linguaggio funzionale puro
l'assegnazione esplicita risulta addirittura completamente assente (si utilizza soltanto
il passaggio dei parametri). In tale modello rivestono particolare importanza la ricorsione,
in pratica l'utilizzo di funzioni che richiamano se stesse e, come struttura dati, la
lista (sequenza ordinata di elementi). Il più importante rappresentante di questa
categoria è senz'altro il Lisp (LISt Processing).
Dichiarativi (o logici): il programma è
considerato come la dimostrazione della verità di una asserzione; il sorgente è
costituito da una sequenza di asserzioni di fatti e regole. Non è necessario indicare
esplicitamente il flusso di esecuzione, ma dato un obiettivo di partenza (il goal)
è il sistema che cerca di individuare i fatti e le regole rilevanti. In tale ricerca
assumono importanza meccanismi quali il pattern matching (in italiano potremo
tradurre in combaciamento di forme) e il backtracking (in pratica se il
sistema durante la ricerca entra in un vicolo cieco, ritorna alla scelta fatta più
recentemente e prova ad applicare la regola o il fatto seguente).
Il fatto che vi sia una netta separazione tra la parte dichiarativa (il
cosa fare) e la parte procedurale (il come) rende un programma scritto in un linguaggio
logico particolarmente leggibile.
I linguaggi logici risultano particolarmente adatti a risolvere
problemi che riguardano entità e le loro relazioni. Mentre nelle
normali applicazioni risultano essere di difficile utilizzo, anche perché, ma non solo,
comportano un modo di programmare completamente diverso dal solito.
L'esemplare più noto di questa famiglia è il Prolog (PROgramming in
LOGic).
Strutturati: man mano che l'ars programmandi
si sviluppava si sono individuate delle metodologie appropriate. Le più importanti sono
la programmazione strutturata e la programmazione ad oggetti. La
programmazione strutturata è una tecnica il cui scopo è di semplificare la struttura dei
programmi, limitando l'uso delle strutture di controllo a pochi casi semplici, tutti con
un solo ingresso e una sola uscita. Tali metodologie sono state in seguito immerse in
nuovi (e in già esistenti) linguaggi, dandone un esplicito supporto. Ad esempio per
evitare l'uso indiscriminato del malefico GOTO (salto incondizionato) sono state
introdotte istruzioni di controllo del flusso più strutturate come il WHILE, FOR e
l'UNTIL.
La maggior parte dei linguaggi oggi diffusi sono strutturati, anche se
spesso permettono comunque l'uso di strutture di controllo non strutturate in quanto la
pratica ha dimostrato che, anche se raramente, vi sono dei casi in cui, ad esempio, con un
GOTO si ottiene una maggiore leggibilità del codice. Ciò avviene soprattutto nella
gestione delle eccezioni (ad esempio nel trattamento delle situazione di errore).
Orientati ad oggetti: il programma è
considerato l'effetto dell'interazione di un insieme di oggetti (insiemi di dati e
algoritmi che manipolano questi dati) che comunicano con l'esterno mediante messaggi.
Assumono rilevanza concetti quali incapsulamento, ereditarietà [si possono costruire
oggetti che ereditano le caratteristiche di un'altro] e polimorfismo.
Oltre a linguaggi specializzati che implementano i principi di tale
metodologia (Smalltalk), sono nate delle estensioni dei linguaggi già esistenti, che li
integrano (ad es. C++ per il C, CLOS per il Lisp).
Equazionali: il programma è considerato come la
risoluzione di equazioni.
Paralleli: in tali linguaggi vi sono dei
meccanismi espliciti per indicare compiti che possono essere effettuati in parallelo. Il
più diffuso è senz'altro l'Occam.
Event driven: una delle cause della difficoltà
di programmare in Windows è che si lavora in un ambiente event driven (orientato
agli eventi) con linguaggi che non supportano direttamente tale paradigma. In un ambiente
event driven non esiste più una sequenza determinata di comandi da eseguire ma una serie
di reazioni che il sistema ha rispondendo a determinati stimoli esterni o interni.
Questo è il segreto dell'enorme successo che ha riscosso il Visual
Basic della Microsoft, permettendo di creare facilmente applicazioni sotto Windows,
proprio perché supporta direttamente il flusso event driven.
Atipici: ad esempio, anche i fogli elettronici
possono essere considerati linguaggi di programmazione in cui, in una certa misura, le
relazioni temporali sono sostituite da relazioni spaziali (il valore di una cella dipende
dal valore di altre).
Linguaggi visivi: in cui si utilizzano costrutti
e grammatiche di tipo grafico o iconico.
Nel prossimo articolo vedremo quali sono i fattori con cui possiamo "valutare"
un linguaggio di programmazione. Tali caratteristiche le divideremo in due categorie: i
fattori intrinseci ed i fattori ambientali.
© Massimo Di Bello <Prometheo
Staff>
mdibello@prometheo.it
Articolo#02: [pubblicato il:
26/05/99]
|