Chiusura (informatica)
Da Wikipedia, l'enciclopedia libera.
Nei linguaggi di programmazione, una chiusura è una astrazione che combina una funzione e un ambiente a scoping lessicale (o statico) legato ad essa. Le variabili nell'ambiente a scoping lessicale hanno lo scopo di conservare uno stato tra le varie chiamate alla funzione. Diversamente dalle funzioni classiche, le chiusure possono salvare informazioni tra le diverse chiamate. Le variabili lessicali conservate nell'ambiente della chiusura, differiscono dalle variabili globali per il fatto che non occupano (o inquinano) il namespace delle variabili globali e differiscono dagli attributi degli oggetti nei linguaggi orientati agli oggetti per il fatto che sono legati a funzioni e non ad oggetti. Le chiusure si rivelano particolarmente utili quando una funzione ha bisogno di "ricordare" informazioni derivate dalle precedenti chiamate. Un uso comune è quello di funzioni che hanno bisogno di un comportamento speciale la prima volta che vengono invocate ma non le successive; le chiusure permettono quindi alla funzione di "ricordare" che hanno già svolto quella azione "speciale" la prima volta che sono state chiamate.
Indice |
[modifica] Teoria e implementazione
Le chiusure, tipicamente, sono implementate con una speciale struttura dati che contiene un puntatore al codice della funzione ed una rappresentazione dell'ambiente lessicale della stessa al momento della sua creazione (per esempio l'insieme delle variabili disponibili e dei relativi valori).
Le chiusure sono legate molto da vicino agli attori del modello ad attori nella computazione concorrente, dove i valori delle variabili nell'ambiente lessicale della funzione sono chiamati conoscenti dell'attore. Un'importante questione sulle chiusure nei linguaggi a paradigma concorrente è se le variabili di una chiusura possono essere aggiornate e, in tal caso, come possono essere sincronizzati questi cambiamenti. Gli attori sono una soluzione a questo problema (Will Clinger 1981).
[modifica] Chiusure e funzioni
Le chiusure, solitamente, sono disponibili in quei linguaggi che permettono alle funzioni di essere oggetti di prima classe (first-class object), cioè di poter essere trattate come i tipi più comuni (stringhe, interi, ecc.): passate come parametri ad altre funzioni, restituite come valori da altre funzioni, associate a variabili, ecc.
In ML, il seguente codice definisce una funzione f che restituisce il parametro passatogli più 1:
fun f(x) = x + 1;
Questa funzione potrebbe anche catturare l'associazione nome/valore dell'ambiente nel quale viene definita, producendo pertanto una chiusura. Per esempio, in questo frammento di codice:
val x = 1; fun f(y) = x + y;
la struttura dati chiusura che rappresenta f contiene un puntatore all'ambiente di definizione, in cui x è legata al valore 1. Quindi, f restituirà sempre il suo parametro più 1, anche se l'ambiente in cui viene applicata ha un valore differente assegnato ad x. Si consideri il seguente codice:
let val x = 1; fun f(y) = x + y; in let val x = 2; in f(3) end end
La chiamata alla funzione f con parametro 3 avviene in un ambiente (il let più interno) in cui x vale 2, però la chiusura f è stata creata in un ambiente (il let più esterno) dove x valeva 1, per questo motivo, il risultato ottenuto è 4, non 5.
[modifica] Utilizzi delle chiusure
Le chiusure hanno molti utilizzi:
- Possono essere usate per aumentare l'astrazione e la versatilità di alcune funzioni. Per esempio, una funzione per l'ordinamento di una lista di valori può accettare una chiusura come parametro, la quale confronta due valori da ordinare secondo un criterio definito dall'utente.
- Dato che una chiusura posticipa la sua valutazione (in inglese, evaluation) (cioè, non "fa" niente fin quando non viene invocata), può essere utilizzata per definire strutture di controllo. Per esempio, tutte le strutture di controllo standard dello Smalltalk, incluse quelle condizionali (if/then/else) e i cicli (while e for), sono definite usando oggetti i cui metodi accettano delle chiusure. I programmatori, quindi, possono anch'essi scrivere delle nuove strutture di controllo.
- Possono essere realizzate più funzioni che "racchiudono" lo stesso ambiente, permettendo loro di comunicare privatamente modificando l'ambiente che condividono.
Nota: a volte vengono definite chiusure anche le strutture dati che combinano un ambiente lessicale con entità che non sono funzioni, ma comunque il termine solitamente si riferisce specificamente alle funzioni.
[modifica] Linguaggi di programmazione con le chiusure
Lo Scheme è stato il primo linguaggio di programmazione ad avere delle chiusure pienamente generali e con scoping lessicale. Virtualmente, tutti i linguaggi di programmazione funzionali e tutti i linguaggi di programmazione orientati agli oggetti derivati dallo Smalltalk supportano qualche forma di chiusura. Alcuni tra i maggiori linguaggi di programmazione a supportare le chiusure sono:
- ActionScript
- AppleScript
- Boo
- C# dalla versione 2.0
- Eiffel
- Groovy
- Haskell
- JavaScript
- Lisp (inclusi Scheme e Common LISP)
- Lua
- Matlab
- Tutte le varianti di ML (inclusi OCaml e Standard ML)
- Nemerle
- Oz
- Perl
- Python
- Ruby
- Sleep
- Smalltalk
[modifica] Simulare le chiusure
In C è possibile usare la parola chiave "static" prima della dichiarazione di una variabile locale. Una variabile static conserva il suo valore attraverso le varie chiamate alla funzione. Questo vale anche per il C++.
int Function( int arg ) { static int numDiChiamate = 0; static struct Stato infoSulloStato; return ++numDiChiamate; }
Alcuni linguaggi orientati agli oggetti permettono al programmatore di usare oggetti che simulano alcune caratteristiche delle chiusure. Per esempio:
- Il Java permette al programmatore di definire "classi anonime" all'interno di un metodo; una classe anonima può fare riferimento ai nomi dichiarati nelle classi nelle quali è racchiusa e ai nomi di oggetti di tipo final dichiarati nel metodo nel quale è racchiusa.
public interface Function<Da,A> { A apply(Da unParametro); } ... public final Function<Integer,Integer> creaSommatore(final int x) { return new Function<Integer,Integer>() { public Integer apply(Integer unParametro) { return x + unParametro; } }; } ...
(Si noti che questo esempio usa degli aspetti del linguaggio disponibili solo a partire dalla versione 1.5/5.0 di Java, come generics, l'autoboxing e l'autounboxing)
- In C++ e D, i programmatori possono definire classi che fanno l'overload dell'operatore () (operatore di applicazione della funzione). Questo genere di classi sono chiamate oggetti funzione o, occasionalmente, funtori (anche se quest'ultimo termine è ambiguo, dato che in altri linguaggi ha un significato molto diverso). Questi oggetti funzione si comportano in modo simile alle funzioni nei linguaggi di programmazione funzionali, ma sono diversi dalle chiusure nel fatto che le variabili del loro ambiente non vengono "catturate". Per simulare una vera chiusura, si può immaginare di mettere tutte le variabili globali in una singola struttura (struct) e di passarne una copia ad un oggetto funzione.
- Il C# permette ad un delegato (delegate) di memorizzare un riferimento ad un metodo di un'istanza di una classe (cioè un oggetto); quando verrà chiamato questo delegato, il metodo verrà invocato per quella particolare istanza. Per simulare una vera chiusura, si può creare un'istanza di una classe (cioè un oggetto) con gli attributi necessari. Un altro modo per simulare le chiusure è stato introdotto dalla versione 2.0 del linguaggio, con l'utilizzo dei metodi anonimi.
[modifica] Voci correlate
[modifica] Bibliografia
- Will Clinger. Foundations of Actor Semantics. MIT Mathematics Doctoral Dissertation. June 1981.