Vorrei sapere a quale generazione di linguaggi un linguaggio di programmazione ad oggetti appartiene e in base a che criterio si può fare questa catalogazione. In particolare Java, C++, Visual Basic, JavaScript in quale generazione di linguaggi sono inseriti.

La
domanda è interessante, e merita una attenta analisi storica dell’evoluzione
dei linguaggi di programmazione.
Agli inizi i calcolatori erano molto costosi, giganteschi e tremendamente limitati.
Non esistevano monitor, ne vere tastiere, la quantità di memoria per i
programmi e i loro dati era limitata a pochissimi kb. Ma anche quei dinosauri
dovevano essere programmati per fare qualcosa di utile, per cui gli scienziati
dell’epoca erano costretti a scrivere degli algoritmi in pseudo linguaggi, su
carta che poi manualmente traducevano in istruzioni sempre più semplici,
fino a raggiungere il livello delle istruzioni comprese direttamente dal microprocessore.
In altre parole si programmava in quello che viene definito linguaggio macchina,
e che si può senz’altro definire la prima generazione di linguaggi, molto
variabili soprattutto perché dipendenti dalla macchina sottostante, nella
completa assenza di qualsiasi sistema operativo, in cui le istruzioni venivano
scritte direttamente in codici binari.
Successivamente si cercò di semplificare la programmazione, creando un
linguaggio che introducesse, un maggiore livello di astrazione, permettendo l’uso
di simboli al posto dei codici binari. Così nacquero i primi linguaggi
assembler, ancora molto rozzi, ma sicuramente più semplici da usare per
la definizione di algoritmi, i codici delle istruzioni diventarono delle abbreviazioni
testuali più facili da leggere e ricordare, e le variabili acquistarono
dei nomi più significativi, rispetto ai loro semplici indirizzi in memoria.
Con l’evoluzione dei sistemi, aumentarono le potenze di calcolo e le dimensioni
della memoria. Inoltre iniziarono a comparire i primi dispositivi di registrazione
di massa dei dati (dischi e nastri magnetici, ecc.). Di pari passo le mire dei
ricercatori, crebbero e la necessità di scrivere algoritmi più complessi
e potenti, nonché il bisogno di riutilizzare il codice già scritto
o condividere con altri il proprio codice, si rese più importate.
A questo scopo vennero pensati dei linguaggi ancora più astratti che ignorassero
del tutto le istruzioni comprese dal processore, assumendo che alcune operazioni
di base fossero disponibili ovunque o che se non altro si potessero implementare
con quelle esistenti. In questo processo di astrazione tutte le variabili non
furono più visibili come semplici indirizzi di memoria, ma solo con i loro
nomi simbolici, e furono introdotti i primi concetti di codice strutturato per
il controllo del flusso di esecuzione delle istruzioni. I più linguaggi
conosciuti di questo tipo sono il BASIC e il FORTRAN, in cui vediamo per la prima
volta la possibilità di definire dei blocchi di codice che possiamo eseguire
ripetutamente in base a condizioni definibili in modo decisamente evoluto, tramite
espressioni di controllo molto simili a funzioni matematiche. Queste espressioni
possono essere usate per decidere se eseguire un blocco di codice o no. Ma soprattutto
mettono in pratica uno dei primi teoremi della computer science, quello di Böhm-Jacopini.
Parallelamente a questi linguaggi definiti imperativi, perché contengono
solo comandi ben precisi da eseguire in un certo ordine, nasce un’altra classe
di linguaggi detti logici, in cui non esistono procedure rigide, ma solo un elenco
di regole di cui non si può stabilire a priori l’ordine di applicazione.
Uno di questi linguaggi è il Prolog, e rappresenta una vera rivoluzione
del modo di pensare l’elaborazione automatica dei dati. In un programma Prolog,
si definiscono tutte le regole applicabili nelle diverse situazioni che si possono
incontrare in un problema. Ognuna delle quali per essere applicata deve soddisfare
un elenco di condizioni organizzate con operatori logici (or, and, not, xor),
e alla cui applicazione può corrispondere un cambiamento del problema di
partenza, che quindi si trasforma fino a raggiungere uno stato che possiamo definire
soluzione.
Nonostante queste sue caratteristiche sia il Prolog che i linguaggi della sua
classe non ebbero un grande successo, per il motivo fondamentale che la maggior
parte dei problemi reali, come il controllo di processi industriali o l’archiviazione
di dati è gestibile in modo più semplice e sicuro con linguaggi
imperativi.
I linguaggi logici fondamentalmente furono pensati per esplorare le capacità
di pensiero artificiale, e infatti furono e sono tuttora strumenti di nicchia
in intelligenza artificiale.
Tornando ai linguaggi imperativi, BASIC, Fortran e simili, permisero senza dubbio
un passo avanti considerevole, ma soffrivano ancora di una primitiva strutturazione
e spingevano all’uso di un’istruzione che rendeva poco chiaro il codice e che
fece coniare il termine di programmazione spaghetti per via delle imprevedibili
diramazioni del flusso di esecuzione. Quest’istruzione è il GOTO, tradotto
letteralmente in “vai a”, ossia un salto incondizionato ad una certa
istruzione. I programmi che più soffrivano di questa “malattia”
erano quelli scritti in BASIC, ma anche gli altri non ne erano immuni.
Per limitarne l’uso furono introdotti linguaggi sempre più strutturati,
come il Pascal, molto usato nella didattica, in cui rispetto ai primi BASIC, c’è
l’introduzione delle funzioni e procedure che permettono agevolmente di evitare
l’uso del GOTO, presente anche in Pascal, ma con una piccola modifica che ne permette
un uso più disciplinato, ossia la necessità di definire un punto
di arrivo del salto, le label(ricordo che in basic si specificava solo il numero
di riga e se per qualche motivo si aggiungeva o si cancellava del codice i salti
a numero di riga diventavano imprevedibili, portando il flusso di esecuzione in
punti del tutto sbagliati).
Nonostante tutti questi sforzi per strutturare e astrarre i linguaggi di programmazione,
rimaneva sempre la necessità di poter controllare l’hardware sottostante
senza limiti per sfruttarlo al meglio, ma di certo non era opportuno tornare all’assembler
e rinunciare ai benefici della programmazione strutturata. Per risolvere questi
problemi fu inventato un linguaggio a metà strada, ancora frequentemente
usato in molti tipi di applicazioni, sto parlando del C.
Il C nelle sue prime versioni forniva tutto quello che ci si aspettava per una
programmazione strutturata, e quindi cicli, espressioni di controllo, esecuzione
condizionata di blocchi di codice, funzioni, strutture dati (presenti anche in
pascal) ossia la possibilità di dare un nome ad un gruppo di variabili
e gestirle come oggetti compatti. Ma nonostante questo manteneva un profondo legame
con l’assembler, comprendendo funzioni a bassissimo livello come lo shift dei
bit di una variabile, o la possibilità di indicare al compilatore che una
variabile sarebbe stato opportuno non fosse allocata in memoria, ma fosse direttamente
un registro del processore. Purtroppo soffriva di una quasi totale assenza di
controllo dei tipi delle variabili, in altre parole si poteva tranquillamente
assegnare ad un intero il valore di una stringa, senza che venisse generato neanche
un messaggio di avvertimento per l’assegnazione ad una variabile di un tipo il
valore di un’altra di tipo del tutto differente.
Dopo questa generazione di linguaggi strutturati, i progetti da sviluppare nella
vita reale divennero sempre più complessi e di grandi dimensioni, di pari
passo all’evoluzione delle macchine. Così cominciò a farsi strada
un’altra rivoluzione, che porta il nome di programmazione ad oggetti.
L’idea di base è che tutti i programmi non fanno altro che definire gruppi
di funzioni che manipolano pacchetti di dati spesso strutturati a più livelli,
ossia strutture che contengono altre strutture e così via. Così
si cominciò a pensare che non aveva senso separare le strutture dati dalle
funzioni che le manipolano, è così nacque il concetto di oggetto
nel software.
Un oggetto definiva i suoi dati e tutte le funzioni che erano state progettate
per la loro gestione. Il tutto in un blocco unico, al punto tale che le funzioni
era come se non esistessero senza la creazione dell’oggetto a cui appartenevano.
Inoltre sia le variabili che le funzioni potevano essere pubbliche o private,
cioè visibili e utilizzabili dall’esterno o no, introducendo il concetto
di information hiding. La rivoluzione non finisce qui, infatti ci si rese conto
anche, che molto spesso si utilizzavano oggetti simili in diversi progetti, ma
anche in uno stesso progetto. Per cui si pensò alla possibilità
di definire oggetti che derivavano da altri, a cui aggiungevano capacità
(funzioni), dati o semplicemente ridefinendone alcune funzioni. Con queste caratteristiche
furono introdotti altri due concetti fondamentali della programmazione ad oggetti:
l’ereditarietà e il polimorfismo.
Forse il primo linguaggio che introdusse la vera programmazione ad oggetti fu
lo SmallTalk, successivamente fu esteso il linguaggio C, dando origine al C++
che in realtà non sposa completamente la filosofia ad oggetti, permettendo
la definizione di elementi non appartenenti a nessun oggetto, cosa che lo rende
un poco pulito da questo punto di vista.
Successivamente nacquero linguaggi come Java, che in questo processo di astrazione
aggiunge un ulteriore elemento.
Dal punto di vista della sintassi è praticamente identico al C++, semanticamente
ha alcune differenze da tenere ben presenti per un suo corretto uso. Per prima
cosa in Java si tagliano completamente i ponti con l’hardware, croce e delizia
del C++, e si astrae la macchina definendo un processore virtuale o per meglio
dire una macchina virtuale. Per fare una cosa del genere si introdusse un software
(necessario per far girare le applicazioni scritte in Java), che si comportava
come una semplice processore hardware.
In poche parole un programma viene compilato in linguaggio macchina, ma non per
la macchina dove si sta lavorando, ma per la macchina virtuale, questo linguaggio
macchina è molto simile a quello vero, per cui la sua esecuzione è
molto veloce, essendoci una corrispondenza di quasi 1:1 (ovviamente il rapporto
varia da piattaforma a piattaforma, o per meglio dire dalla complessità
del processore reale). Anche in questo modo, il fatto che ci sia un interprete
delle istruzioni purtroppo ne limita le performance, ma in compenso permette una
sicurezza totale (se la macchina virtuale è stata implementata correttamente),
e una completa indipendenza dalla macchina che comunque deve avere delle caratteristiche
minime, scelte in modo da abbracciare la quasi totalità delle macchine
moderne.
Java a parte la sintassi molto simile al C++, porta con se delle notevoli innovazioni
per rendere più difficile commettere errori e aumentare conseguentemente
la produttività. Per prima cosa non è più possibile vedere
indirizzi di memoria, tutte le variabili fanno riferimento a oggetti, ma non è
possibile leggerne l’indirizzo, ne spostarlo di un numero di byte a piacere. L’unica
operazione ammessa con le variabili(riferimenti a locazioni di memoria) è
la copia da una a l’altra o la cancellazione del riferimento impostandolo a null.
Questo impedisce ai programmi di accedere a qualsiasi locazione di memoria, eliminando
la possibilità di provocare grossi danni al sistema. Inoltre la gestione
degli errori è completamente affidata alle eccezioni, e infine sono previsti
meccanismi di sincronizzazione per i thread direttamente nel linguaggio.
Per finire la deallocazione della memoria è gestita automaticamente, con
un processo chiamato Garbage Collector che solleva il programmatore dalla gestione
della distruzione degli oggetti.
La critica maggiore che si può fare a Java, è che ha spinto l’astrazione
a livelli eccessivi a scapito della efficienza del codice, ma d’altro canto grazie
a questo, e ai limiti nell’accesso alla macchina, permette di evitare molti errori
e aumenta notevolmente la produttività dei programmatori, e nello stesso
tempo permette la creazione di applicazioni molto robuste.
Parallelamente a questi linguaggi si posizionano gli script, come il Visual Basic
Script, Java Script, ecc. che in realtà non definirei dei veri e propri
linguaggi. Infatti non permettono la definizione di vere applicazioni, ma semplicemente
di definire dei comportamenti particolari in software che li comprendono, come
i browser internet e quindi sono molto dipendenti dalla versione di questi ultimi.
Comunque la loro utilità è indubbia, infatti spesso non si ha bisogno
di una vera applicazione, ma semplicemente di effettuare delle piccole procedure
in programmi particolari, e uno script è di sicuro una buona soluzione,
se non l’unica.
Di linguaggi script, come è facile immaginare, ne esistono infiniti, è
spesso le applicazioni professionali ne supportano uno, vedi lo scripting dei
programmi di grafica come 3D Studio, 3D Studio MAX, AutoCAD, Maya, ecc. Ognuno
di questi programmi permette la definizione di procedure per gestire animazioni
o creare superfici e curve irrealizzabili a mano con la precisione necessaria.
Infine esistono i meta linguaggi, usati in tools per la creazione di interpreti
o compilatori, come Flex/Bison o ANTLR, con cui si definiscono regole grammaticali,
con procedure da eseguire quando vengono applicate, che non si possono definire
script in quanto, con questi meta linguaggi vengono generati i sorgenti di vere
e proprie applicazioni che comprendono altri linguaggi (interpreti). Ma la cosa
che li rende particolarmente interessanti è che in realtà generano
i sorgenti di queste applicazioni (per esempio ANTLR permette la generazione sia
di codice Java che C/C++), cosa che permette la loro integrazione in progetti
di più ampio respiro (vedi le applicazioni grafiche accennate prima).
Spero che questa breve storia dei linguaggi permetta una visione più chiara
del panorama attuale, e che non sia stata troppo noiosa, ma soprattutto spero
che piaccia ai lettori, almeno quanto è piaciuto a me scriverla.