2009-07-08 11 views
17

Ho letto gli argomenti precedenti sulle chiusure su stackflow e altre fonti e una cosa mi confonde ancora. Da quello che sono stato in grado di mettere insieme tecnicamente una chiusura è semplicemente l'insieme di dati contenenti il ​​codice di una funzione e il valore di variabili vincolate in quella funzione.Qual è l'esatta definizione di chiusura?

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

int count() 
{ 
    static int x = 0; 

    return x++; 
} 

Eppure tutto quello che ho letto sembra implicare la chiusura deve in qualche modo coinvolgere funzioni passando come oggetti di prima classe. 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?

+1

Wikipedia è particolarmente buono a definizioni: http://en.wikipedia.org/wiki/Closure_%28computer_science%29 –

+6

Tuttavia, come definizioni di Wikipedia questo tende ad essere scritto da fanatici di stress da pipa e da weenies di cristallografia (cioè non sono sempre gli uccelli più facili da capire). –

+1

L'articolo di Wikipedia era uno che ho letto. Dice che qualsiasi funzione di prima classe con variabili libere è una chiusura. Ciò non ha alcun senso, anche perché include tutte le funzioni di prima classe che restituiscono valori basati sui loro argomenti. Sta implicando che diventa una chiusura quando la funzione usa variabili libere per istanziare le sue variabili associate? – Amaron

risposta

6

Da quello che 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 chiude sopra le variabili libere (variabili che non sono variabili locali)

+0

Anche se non usa quelle variabili? In altre parole, una chiusura dovrebbe mantenere la propria copia di ogni globale accessibile nel caso in cui il globale cambi? – Amaron

+2

La chiusura ha accesso alla variabile nel contesto * defined *, non nel contesto * calling *. In teoria, è possibile definire chiusure in un ambito e accesso in un altro, dando quindi un modo per aggirare le regole dell'ambito, anche se non tutti i linguaggi di programmazione lo consentono. –

+0

@Amaron: non "è la propria copia" - è la stessa copia. Se ne modifichi uno, ne modifichi l'altro. (Anche in questo caso, non tutti i linguaggi di programmazione lo consentono, ma questo è il modo in cui è definita la chiusura) –

22

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

Ecco come una chiusura avrebbe funzionato:

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, diverse invocazioni di makeCounter() producono diverse funzioni con la propria vincolante di variabili nel loro ambiente lessicale che essi hanno "chiuso over".

Edit: Penso che esempi come questo rendano le chiusure più facili da capire rispetto alle definizioni, ma se si desidera una definizione direi, "Una chiusura è una combinazione di una funzione e di un ambiente. definiti nella funzione e quelli che sono visibili alla funzione al momento della sua creazione. Queste variabili devono rimanere disponibili per la funzione finché esiste la funzione. "

+0

Questa è davvero una chiusura, ma stavo cercando la definizione rigorosa esatta di ciò che è e non è una chiusura. L'esempio che ho dato voleva dire qualcosa che pensavo dovesse essere una chiusura, ma sapevo che probabilmente non era una chiusura. Ti inviterò ancora per un esempio chiaro e utile che non implichi la chiarezza. – Amaron

12

Per il exact definition, I suggest looking at its Wikipedia entry. È particolarmente buono. Voglio solo chiarirlo con un esempio.

assumersi questa C# frammento di codice (questo dovrebbe eseguire una ricerca AND in una lista):

List<string> list = new List<string> { "hello world", "goodbye world" }; 
IEnumerable<string> 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); 

E 'un errore comune in C# per fare qualcosa di simile. Se si guarda l'espressione lambda all'interno di Where, si noterà che definisce una funzione il cui comportamento dipende dal valore di una variabile nel suo sito di definizione. È come passare una variabile stessa alla funzione, anziché il valore di tale 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" e "arrivederci mondo", che non è quello che volevamo. Quello che è successo? Come detto in precedenza, la funzione abbiamo dichiarato con l'espressione lambda è una chiusura sopra keywordvariabile quindi questo è ciò che accade:

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

e al momento dell'esecuzione chiusura, keyword ha il valore mondo" , "quindi stiamo fondamentalmente filtrando la lista 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)); 
} 

Da temporaryVariable ambito la corpo del ciclo foreach, in ogni iterazione, è una variabile diversa. In effetti, ogni chiusura si legherà a una variabile distinta (quelle sono istanze differenti di 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 "mondo" al momento dell'esecuzione di chiusura.

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

+3

Post fantastico. Chiaro, perspicace. Molto bene. – jason

+0

A partire da.NET Framework v4.5.2 il primo snippet di codice scrive solo Hello World. FilteredList è un {System.Linq.Enumerable.WhereListIterator } dopo il primo ciclo foreach in entrambi gli snippits di codice e viene risolto durante il secondo ciclo foreach – JnJnBoo

1

Ottima domanda! Dato che uno dei principi OOP di OOP è che gli oggetti hanno comportamenti e dati, le chiusure sono un tipo speciale di oggetto 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 impressionante)

Al livello più semplice una stretta è in realtà solo un codice che è avvolto fino a diventare un oggetto androgina /metodo. È un metodo perché può prendere parametri e restituire un valore, ma è anche un oggetto in cui è possibile passare un riferimento ad esso.

Nelle parole di Dierk, immagina una busta con dentro un pezzo di carta. Un oggetto 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) 

La chiusura oggetto qui è il valore della variabile busta ed è uso è che è un parametro per l'ogni metodo.

Alcuni dettagli: Ambito: l'ambito di una chiusura sono i dati ei membri a cui è possibile accedere al suo interno. Ritorno da una chiusura: le chiusure utilizzano spesso un meccanismo di callback da eseguire e restituire da sé. Argomenti: se la chiusura richiede solo 1 param, Groovy e altri lang forniscono un nome predefinito: "it", per rendere la codifica più veloce. Così, per esempio, nel nostro esempio precedente:

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

Spero che questo è quello che stai cercando!

0

Penso che Peter Eddy abbia ragione, ma l'esempio potrebbe essere reso più interessante. È possibile definire le funzioni due che si chiudono su una variabile locale, incrementare il 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, è possibile 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.

2

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, tra cui le funzioni vengono calcolate in un environement, Un ambiente è una sequenza di fotogrammi . 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. Immaginate di avere una funzione chiamata accumulatore, che una volta valutato, tornerà 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 valutiamo la seguente riga?

accum1 = accumulator(0); 

Prima un nuovo ambiente viene creato e un oggetto intero (per contatore) è destinata a 0 nel suo 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 contiene un riferimento ad esso, in quanto ha bisogno di accedere alla variabile contatore. Quando viene chiamato accum1, il valore di contatore viene incrementato nel valore 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 . (Vedi i messaggi "Disegni pericolosi" e "Su messaggio che passa").

2

C'è un sacco di risposte già, ma io aggiungo un altro nessuno ...

chiusure non sono unici per 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 locali del padre, in particolare ai valori che tali variabili avevano quando veniva eseguita la riga che creava il riferimento alla funzione, anche se tali variabili non esistono più all'uscita del genitore.

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

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

un pitone lambda è un semplice esempio funzionale, in stile ...

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

funcref = parent() 
print funcref() 

miei Pythons un po 'arrugginito, ma credo che è di destra. Il punto è che la funzione nidificata (la lambda) si riferisce ancora al valore della variabile locale a anche se parent è terminato 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.

1

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

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

Problemi correlati