Qual è l’esatta definizione di chiusura?

Ho letto gli argomenti precedenti sulle chiusure su stackflow e altre fonti e una cosa mi confonde ancora. Da quello che sono riuscito a mettere insieme tecnicamente una chiusura è semplicemente l’insieme di dati contenenti il ​​codice di una funzione e il valore delle variabili associate in quella funzione.

In altre parole tecnicamente la seguente funzione C dovrebbe essere una chiusura dalla mia comprensione:

int count() { static int x = 0; return x++; } 

Tuttavia tutto ciò che leggo sembra implicare chiusure deve in qualche modo coinvolgere le funzioni di passaggio come oggetti di prima class. Inoltre, di solito sembra implicito che le chiusure non facciano parte della programmazione procedurale. Si tratta di un caso di una soluzione eccessivamente associata al problema che risolve o sto fraintendendo la definizione esatta?

Da quanto ho capito, una chiusura deve anche avere accesso alle variabili nel contesto di chiamata. Le chiusure sono solitamente associate alla programmazione funzionale. Le lingue possono avere elementi provenienti da diversi tipi di prospettive di programmazione, funzionali, procedurali, imperative, dichiarative, ecc. Il loro nome viene chiuso in un contesto specifico. Possono anche avere un legame lessicale in quanto possono fare riferimento al contesto specificato con gli stessi nomi utilizzati in quel contesto. Il tuo esempio non ha riferimenti a nessun altro contesto ma a uno statico globale.

Da Wikipedia

Una chiusura si chiude sulle variabili libere (variabili che non sono variabili locali)

No, non è una chiusura. Il tuo esempio è semplicemente una funzione che restituisce il risultato dell’incremento di una variabile statica.

Ecco come funzionerebbe una chiusura:

 function makeCounter( int x ) { return int counter() { return x++; } } c = makeCounter( 3 ); printf( "%d" c() ); => 4 printf( "%d" c() ); => 5 d = makeCounter( 0 ); printf( "%d" d() ); => 1 printf( "%d" c() ); => 6 

In altre parole, le diverse invocazioni di makeCounter () producono funzioni diverse con il proprio legame di variabili nel loro ambiente lessicale che hanno “chiuso”.

Edit: Penso che esempi come questo rendano le chiusure più facili da capire rispetto alle definizioni, ma se vuoi una definizione direi, “Una chiusura è una combinazione di una funzione e di un ambiente .L’ambiente contiene le variabili che sono definite nella funzione così come quelli che sono visibili alla funzione quando è stata creata. Queste variabili devono rimanere disponibili per la funzione finché esiste la funzione. ”

Per la definizione esatta, suggerisco di guardare la sua voce su Wikipedia . È particolarmente buono. Voglio solo chiarirlo con un esempio.

Assumi questo snippet di codice C # (che dovrebbe eseguire una ricerca AND in una lista):

 List list = new List { "hello world", "goodbye world" }; IEnumerable filteredList = list; var keywords = new [] { "hello", "world" }; foreach (var keyword in keywords) filteredList = filteredList.Where(item => item.Contains(keyword)); foreach (var s in filteredList) // closure is called here Console.WriteLine(s); 

È una trappola comune in C # fare qualcosa del genere. Se osservi l’espressione lambda all’interno di Where , vedrai che definisce una funzione che il suo comportamento dipende dal valore di una variabile nel suo sito di definizione. È come passare una variabile stessa alla funzione, piuttosto che il valore di quella variabile . In effetti, quando viene chiamata questa chiusura, recupera il valore della variabile keyword in quel momento. Il risultato di questo esempio è molto interessante. Stampa sia “ciao mondo” che “addio mondo”, che non è quello che volevamo. Quello che è successo? Come ho detto sopra, la funzione che abbiamo dichiarato con l’espressione lambda è una chiusura sulla variabile keyword , quindi questo è ciò che accade:

 filteredList = filteredList.Where(item => item.Contains(keyword)) .Where(item => item.Contains(keyword)); 

e al momento dell’esecuzione della chiusura, la keyword ha il valore “mondo”, quindi fondamentalmente stiamo filtrando l’elenco un paio di volte con la stessa parola chiave. La soluzione è:

 foreach (var keyword in keywords) { var temporaryVariable = keyword; filteredList = filteredList.Where(item => item.Contains(temporaryVariable)); } 

Poiché la variabile temporaryVariable è circoscritta al corpo del ciclo foreach , in ogni iterazione, è una variabile diversa. In effetti, ogni chiusura si legherà a una variabile distinta (quelle sono istanze diverse di Variabile temporaryVariable ad ogni iterazione). Questa volta, darà i risultati corretti (“ciao mondo”):

 filteredList = filteredList.Where(item => item.Contains(temporaryVariable_1)) .Where(item => item.Contains(temporaryVariable_2)); 

in cui temporaryVariable_1 ha il valore di “ciao” e temporaryVariable_2 ha il valore “world” al momento dell’esecuzione della chiusura.

Si noti che le chiusure hanno causato un’estensione della durata delle variabili (la loro vita doveva terminare dopo ogni ripetizione del ciclo). Questo è anche un importante effetto collaterale delle chiusure.

Una chiusura è una tecnica di implementazione per rappresentare procedure / funzioni con lo stato locale. Un modo per implementare le chiusure è descritto in SICP. Presenterò comunque il succo di ciò.

Tutte le espressioni, comprese le funzioni sono valutate in un ambiente , Un ambiente è una sequenza di frame . Un frame mappa i nomi delle variabili in valori. Ogni frame ha anche un puntatore al suo ambiente di chiusura. Una funzione viene valutata in un nuovo ambiente con una cornice contenente associazioni per i suoi argomenti. Ora diamo un’occhiata al seguente scenario interessante. Immagina di avere una funzione chiamata accumulatore , che una volta valutata restituirà un’altra funzione:

 // This is some C like language that has first class functions and closures. function accumulator(counter) { return (function() { return ++counter; }); } 

Cosa succederà quando valuteremo la seguente riga?

 accum1 = accumulator(0); 

Prima viene creato un nuovo ambiente e un object intero (per contatore ) è associato a 0 nel primo fotogramma. Il valore restituito, che è una nuova funzione, è associato nell’ambiente globale. Di solito il nuovo ambiente sarà spazzato via una volta terminata la valutazione della funzione. Qui non succederà. accum1 sta tenendo un riferimento ad esso, in quanto ha bisogno di accedere al contatore variabile. Quando viene chiamato accum1 , incrementerà il valore del contatore nell’ambiente di riferimento. Ora possiamo chiamare accum1 una funzione con lo stato locale o una chiusura.

Ho descritto alcuni usi pratici delle chiusure sul mio blog http://vijaymathew.wordpress.com . (Vedi i messaggi “Disegni pericolosi” e “Su messaggio che passa”).

Ci sono già molte risposte, ma ne aggiungerò un’altra a chiunque …

Le chiusure non sono uniche per i linguaggi funzionali. Si verificano in Pascal (e famiglia), ad esempio, che ha procedure annidate. C standard non li ha (ancora), ma IIRC c’è un’estensione GCC.

Il problema di base è che una procedura nidificata può fare riferimento a variabili definite nel suo genitore. Inoltre, il genitore può restituire un riferimento alla procedura nidificata al relativo chiamante.

La procedura nidificata fa ancora riferimento a variabili che erano locali al genitore, in particolare ai valori che tali variabili avevano quando veniva eseguita la linea che creava il riferimento alla funzione, anche se tali variabili non esistono più quando il genitore è uscito.

Il problema si verifica anche se la procedura non viene mai restituita dal padre: diversi riferimenti alla procedura nidificata costruita in momentjs diversi potrebbero utilizzare valori passati diversi delle stesse variabili.

La risoluzione a questo è che quando si fa riferimento alla funzione nidificata, essa viene impacchettata in una “chiusura” contenente i valori delle variabili necessari per un secondo momento.

Un lambda Python è un semplice esempio di stile funzionale …

 def parent () : a = "hello" return (lamda : a) funcref = parent () print funcref () 

I miei pitoni sono un po ‘arrugginiti, ma penso che sia giusto. Il punto è che la funzione nidificata (la lambda) si riferisce ancora al valore della variabile locale a anche se il parent è uscito quando viene chiamato. La funzione ha bisogno di un posto dove conservare quel valore finché non è necessario, e quel luogo è chiamato chiusura.

Una chiusura è un po ‘come un insieme implicito di parametri.

Ottima domanda! Dato che uno dei principi OOP di OOP è che gli oggetti hanno comportamenti e dati, le chiusure sono un tipo speciale di object perché il loro scopo più importante è il loro comportamento. Detto questo, cosa intendo quando parlo del loro “comportamento?”

(Molto di questo è tratto da “Groovy in Action” di Dierk Konig, che è un libro fantastico)

Al livello più semplice, una chiusura è in realtà solo un codice che viene completato per diventare un object / metodo androgino. È un metodo perché può prendere parametri e restituire un valore, ma è anche un object in cui è ansible passare un riferimento ad esso.

Nelle parole di Dierk, immagina una busta con dentro un pezzo di carta. Un object tipico avrebbe le variabili e i loro valori scritti su questo foglio, ma una chiusura avrebbe invece un elenco di istruzioni. Diciamo che la lettera dice “Dare questa busta e una lettera ai tuoi amici”.

 In Groovy: Closure envelope = { person -> new Letter(person).send() } addressBookOfFriends.each (envelope) 

L’ object di chiusura qui è il valore della variabile di inviluppo e il suo uso è che è un parametro per ogni metodo.

Alcuni dettagli: Ambito: l’ambito di una chiusura sono i dati e i membri a cui è ansible accedere al suo interno. Ritorno da una chiusura: le chiusure utilizzano spesso un meccanismo di callback da eseguire e restituire da se stesso. Argomenti: se la chiusura richiede solo 1 param, Groovy e altri lang forniscono un nome predefinito: “it”, per rendere la codifica più veloce. Quindi, ad esempio nel nostro esempio precedente:

 addressBookOfFriends.each (envelope) is the same as: addressBookOfFriends.each { new Letter(it).send() } 

Spero che questo sia quello che stai cercando!

Un object è stato più funzione. Una chiusura, è funzione più stato.

la funzione f è una chiusura quando si chiude su (catturato) x

Penso che Peter Eddy abbia ragione, ma l’esempio potrebbe essere reso più interessante. È ansible definire due funzioni che si chiudono su una variabile locale, incremento e decremento. Il contatore sarebbe condiviso tra quella coppia di funzioni e unico per loro. Se definisci una nuova coppia di funzioni di incremento / decremento, condivideranno un contatore diverso.

Inoltre, non è necessario passare il valore iniziale di x, è ansible lasciare il valore predefinito a zero all’interno del blocco funzione. Ciò renderebbe più chiaro che sta usando un valore che non hai più accesso normale al contrario.