La programmazione procedurale / funzionale non è in alcun modo più debole di OOP , anche senza entrare negli argomenti di Turing (il mio linguaggio ha il potere di Turing e può fare qualsiasi cosa faccia un altro), il che non significa molto. In realtà, le tecniche orientate agli oggetti sono state inizialmente sperimentate in linguaggi che non li avevano integrati. In questo senso, la programmazione OO è solo uno stile specifico di programmazione procedurale . Ma aiuta a far rispettare discipline specifiche, come la modularità, l'astrazione e il nascondere le informazioni che sono essenziali per la comprensione e la manutenzione del programma.
Alcuni paradigmi di programmazione si evolvono dalla visione teorica del calcolo. Un linguaggio come Lisp si è evoluto dal lambda-calcolo e dall'idea della meta-circolarità dei linguaggi (simile alla riflessività nel linguaggio naturale). Le clausole di Horn generarono Prolog e la programmazione dei vincoli. Anche la famiglia Algol deve il lambda-calcolo, ma senza riflessività integrata.
Lisp è un esempio interessante, in quanto è stato il banco di prova di molte innovazioni del linguaggio di programmazione, riconducibile al suo doppio patrimonio genetico.
Tuttavia, le lingue si evolvono, spesso con nuovi nomi. Un importante fattore di evoluzione è la pratica di programmazione. Gli utenti identificano le pratiche di programmazione che migliorano le proprietà dei programmi come leggibilità, manutenibilità, verificabilità della correttezza. Quindi cercano di aggiungere alle lingue funzionalità o vincoli che supporteranno e talvolta applicheranno queste pratiche in modo da migliorare la qualità dei programmi.
Ciò significa che queste pratiche sono già possibili nel vecchio linguaggio di programmazione, ma ci vuole comprensione e disciplina per usarle. Incorporarli in nuovi linguaggi come concetti primari con sintassi specifica rende queste pratiche più facili da usare e da comprendere prontamente, in particolare per gli utenti meno sofisticati (cioè la stragrande maggioranza). Inoltre rende la vita un po 'più semplice per gli utenti sofisticati.
In qualche modo, è progettare la lingua che cosa è un sottoprogramma / funzione / procedura per un programma. Una volta identificato il concetto utile, gli viene dato un nome (possibilmente) e una sintassi, in modo che possa essere facilmente utilizzato in tutti i programmi sviluppati con quella lingua. E quando avrà successo, sarà incorporato anche nelle lingue future.
Esempio: ricreare l'orientamento agli oggetti
Ora provo a illustrarlo su un esempio (che potrebbe certamente essere ulteriormente perfezionato, visto il tempo). Lo scopo dell'esempio non è mostrare che un programma orientato agli oggetti può essere scritto in uno stile di programmazione procedurale, possibilmente a scapito della responsabilità e della manutenibilità. Cercherò piuttosto di mostrare che alcune lingue senza strutture OO possono effettivamente utilizzare funzioni di ordine superiore e struttura di dati per creare effettivamente i mezzi per imitare in modo efficace l'orientamento agli oggetti , al fine di beneficiare delle sue qualità in materia di organizzazione del programma, tra cui modularità, astrazione e occultamento delle informazioni .
Come ho detto, Lisp è stato il banco di prova di molte evoluzioni linguistiche, incluso il paradigma OO (sebbene quella che potesse essere considerata la prima lingua OO era Simula 67, nella famiglia Algol). Lisp è molto semplice e il codice per il suo interprete di base è inferiore a una pagina. Ma puoi fare la programmazione OO in Lisp. Tutto ciò che serve è funzioni di ordine superiore.
Non userò la sintassi esoterica di Lisp, ma piuttosto lo pseudo-codice, per semplificare la presentazione. E considererò un semplice problema essenziale: nascondere le informazioni e la modularità . Definire una classe di oggetti impedendo all'utente di accedere (la maggior parte) dell'implementazione.
Supponiamo che io voglia creare una classe chiamata vettore, che rappresenta vettori bidimensionali, con metodi che includono: aggiunta di vettore, dimensione di vettore e parallelismo.
function vectorrec () {
function createrec(x,y) { return [x,y] }
function xcoordrec(v) { return v[0] }
function ycoordrec(v) { return v[1] }
function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }
function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }
function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }
return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]
}
Quindi posso assegnare il vettore creato ai nomi delle funzioni effettive da utilizzare.
[vector, xcoord, ycoord, vplus, vsize, vparallel] = vectorclass ()
Perché essere così complicato? Perché posso definire nella funzione costrutti intermedi vectorrec che non voglio essere visibile al resto del programma, in modo da preservare la modularità.
Possiamo fare un'altra raccolta in coordinate polari
function vectorpol () {
...
function pluspol (u,v) { ... }
function sizepol (v) { return v[0] }
...
return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]
}
Ma potrei voler usare indifferentemente entrambe le implementazioni. Un modo per farlo è aggiungere un componente di tipo a tutti i valori e definire tutte le funzioni di cui sopra nello stesso ambiente: Quindi posso definire ciascuna delle funzioni restituite in modo che testerà prima il tipo di coordinate, quindi applichi la funzione specifica per questo.
function vector () {
...
function plusrec (u,v) { ... }
...
function pluspol (u,v) { ... }
...
function plus (u,v) { if u[2]='rec' and v[2]='rec'
then return plusrec (u,v) ... }
return [ ..., plus, ...]
}
Cosa ho guadagnato: le funzioni specifiche rimangono invisibili (a causa dell'ambito degli identificatori locali) e il resto del programma può utilizzare solo le più astratte restituite dalla chiamata a vectorclass.
Un'obiezione è che potrei definire direttamente ciascuna delle funzioni astratte nel programma e lasciare all'interno della definizione delle funzioni dipendenti dal tipo di coordinata. Quindi sarebbe anche nascosto. Questo è vero, ma poi il codice per ciascun tipo di coordinate verrebbe tagliato in piccoli pezzi distribuiti sul programma, che è meno ridimensionabile e gestibile.
In realtà, non ho nemmeno bisogno di dare loro un nome e potrei semplicemente mantenere i valori funzionali anonimi in una struttura di dati indicizzata dal tipo e una stringa che rappresenta il nome della funzione. Questa struttura essendo locale al vettore di funzione sarebbe invisibile dal resto del programma.
Per semplificare l'uso, invece di restituire un elenco di funzioni, posso restituire una singola funzione chiamata apply prendendo come argomento un valore di tipo esplicito e una stringa e applicare la funzione con il tipo e il nome corretti. Questo assomiglia molto al chiamare un metodo per una classe OO.
Mi fermerò qui, in questa ricostruzione di una struttura orientata agli oggetti.
Quello che ho cercato di fare è dimostrare che non è troppo difficile costruire l'orientamento agli oggetti utilizzabili in un linguaggio sufficientemente potente, tra cui eredità e altre caratteristiche simili. La metacircolarità dell'interprete può aiutare, ma soprattutto a livello sintattico, che è tutt'altro che trascurabile.
I primi utenti dell'orientamento agli oggetti hanno sperimentato i concetti in questo modo. E questo è generalmente vero per molti miglioramenti ai linguaggi di programmazione. Naturalmente, anche l'analisi teorica ha un ruolo e ha aiutato a capire o affinare questi concetti.
Ma l'idea che le lingue che non dispongono di funzionalità OO sono destinate a fallire in alcuni progetti è semplicemente ingiustificata. Se necessario, possono imitare l'implementazione di queste funzionalità in modo abbastanza efficace. Molte lingue hanno il potere sintattico e semantico di orientare gli oggetti in modo abbastanza efficace, anche quando non è incorporato. E questo è più che un argomento di Turing.
OOP non affronta le limitazioni di altre lingue, ma supporta o applica metodologie di programmazione che aiutano a scrivere programmi migliori, aiutando così gli utenti meno esperti a seguire le buone pratiche che i programmatori più avanzati hanno usato e sviluppato senza quel supporto.
Credo che un buon libro per capire tutto ciò potrebbe essere Abelson & Sussman: struttura e interpretazione dei programmi per computer .