Ogni tanto vedo “chiusure” menzionate, e ho provato a cercarle ma Wiki non dà una spiegazione che capisco. Qualcuno potrebbe aiutami qui?
Commenti
- Se conosci Java / C # spero che questo link ti sia daiuto- http://www.developerfusion.com/article/8251/the-beauty-of-closures/
- Le chiusure sono difficili da capire. Dovresti provare a fare clic su tutti i link nella prima frase di quellarticolo di Wikipedia e comprenderli prima gli articoli.
- stackoverflow.com/questions/36636/what-is-a-closure
- Cosa ‘ è la differenza fondamentale tra una chiusura e una classe, però? Ok, una classe con un solo metodo pubblico.
- @biziclop: potresti emulo una chiusura con una classe (che ‘ è ciò che devono fare gli sviluppatori Java). Ma ‘ di solito sono leggermente meno prolissi da creare e non non devi gestire manualmente ciò che ‘ ti stai occupando. (Gli hardcore lispers fanno una domanda simile, ma probabilmente arrivano a quellaltra conclusione: che il supporto OO a livello di lingua non è necessario quando si hanno chiusure).
Risposta
(Disclaimer: questa è una spiegazione di base; per quanto riguarda la definizione, sto semplificando un po )
Il modo più semplice per pensare a una chiusura è una funzione che può essere memorizzata come una variabile (denominata “prima -class function “), che ha una capacità speciale di accedere ad altre variabili locali nellambito in cui è stata creata.
Esempio (JavaScript):
var setKeyPress = function(callback) { document.onkeypress = callback; }; var initialize = function() { var black = false; document.onclick = function() { black = !black; document.body.style.backgroundColor = black ? "#000000" : "transparent"; } var displayValOfBlack = function() { alert(black); } setKeyPress(displayValOfBlack); }; initialize();
Le funzioni 1 assegnate a document.onclick
e displayValOfBlack
sono chiusure. Puoi vedere che entrambi fanno riferimento alla variabile booleana black
, ma quella variabile è assegnata al di fuori della funzione. Perché black
è locale allambito in cui è stata definita la funzione , il puntatore a questa variabile viene conservato.
Se lo metti in una pagina HTML:
- Fai clic per passare al nero
- Premi [invio] per visualizzare “true”
- Fai nuovamente clic, torna al bianco
- Premi [invio] per visualizzare “false”
Ciò dimostra che entrambi hanno accesso allo stesso black
, e può essere utilizzato per memorizzare lo stato senza alcun oggetto wrapper.
La chiamata a setKeyPress
serve a dimostrare come una funzione può essere passata proprio come qualsiasi variabile. Lo scope conservato nella chiusura è ancora quello in cui è stata definita la funzione.
Le chiusure sono comunemente utilizzati come gestori di eventi, soprattutto in JavaScript e ActionScript. Un buon uso delle chiusure ti aiuterà a legare implicitamente le variabili ai gestori di eventi senza dover creare un wrapper di oggetti. Tuttavia, un uso incauto porterà a perdite di memoria (come quando un gestore di eventi inutilizzato ma preservato è lunica cosa da mantenere su oggetti di grandi dimensioni in memoria, in particolare oggetti DOM, impedendo la raccolta di rifiuti).
1: In realtà, tutte le funzioni in JavaScript sono chiusure.
Commenti
- Mentre leggevo la tua risposta, ho ho sentito una lampadina accendersi nella mia mente. Molto apprezzato! 🙂
- Poiché
black
è dichiarato allinterno di una funzione, ‘ non verrà distrutto quando lo stack si svolge. ..? - @gablin, questo è ciò che è unico nelle lingue che hanno chiusure. Tutti i linguaggi con Garbage Collection funzionano allo stesso modo: quando non vengono più tenuti riferimenti a un oggetto, questo può essere distrutto. Ogni volta che una funzione viene creata in JS, lambito locale è vincolato a quella funzione finché quella funzione non viene distrutta.
- @gablin, quella ‘ è una buona domanda. Non ‘ credo che possano ‘ t & mdash; ma ho sollevato la raccolta dei rifiuti solo perché è quello che usa JS e che ‘ è quello a cui sembravi riferirti quando hai detto ”
black
è dichiarato allinterno di una funzione, ‘ non viene distrutto “. Ricorda anche che se dichiari un oggetto in una funzione e poi lo assegni a una variabile che risiede da qualche altra parte, quelloggetto viene preservato perché ci sono altri riferimenti ad esso. - Objective-C (e C sotto clang) supporta i blocchi, che sono essenzialmente chiusure, senza garbage collection. Richiede il supporto del runtime e alcuni interventi manuali sulla gestione della memoria.
Risposta
Una chiusura è fondamentalmente solo un modo diverso di guardare un oggetto. Un oggetto è un dato a cui sono associate una o più funzioni. Una chiusura è una funzione a cui sono associate una o più variabili. I due sono sostanzialmente identici, almeno a livello di implementazione. La vera differenza sta nella loro provenienza.
Nella programmazione orientata agli oggetti, dichiari una classe di oggetti definendo in anticipo le sue variabili membro e i suoi metodi (funzioni membro), quindi crei istanze di quella classe. Ogni istanza viene fornita con una copia dei dati del membro, inizializzata dal costruttore. Quindi si ha una variabile di un tipo di oggetto e la si passa come un pezzo di dati, perché lattenzione è sulla sua natura di dati.
In una chiusura, daltra parte, loggetto non è definito in anticipo come una classe di oggetti o istanziato tramite una chiamata al costruttore nel codice. Invece, scrivi la chiusura come una funzione allinterno di unaltra funzione. La chiusura può fare riferimento a una qualsiasi delle variabili locali della funzione esterna e il compilatore la rileva e sposta queste variabili dallo spazio dello stack della funzione esterna alla dichiarazione di oggetto nascosto della chiusura. Si dispone quindi di una variabile del tipo di chiusura , e anche se è fondamentalmente un oggetto nascosto, lo passi come riferimento a una funzione, perché il focus è sulla sua natura come funzione.
Commenti
- +1: buona risposta. Puoi vedere una chiusura come un oggetto con un solo metodo e un oggetto arbitrario come una raccolta di chiusure su alcuni dati sottostanti comuni (le variabili membro delloggetto ‘). Penso che questi due punti di vista siano abbastanza simmetrici.
- Ottima risposta. In realtà spiega lintuizione della chiusura.
- @Mason Wheeler: Dove vengono archiviati i dati della chiusura? In pila come una funzione? O nellheap come un oggetto?
- @RoboAlex: nellheap, perché ‘ è un oggetto che sembra una funzione .
- @RoboAlex: la posizione in cui vengono archiviati una chiusura e i dati acquisiti dipende dallimplementazione. In C ++ può essere memorizzato nellheap o nello stack.
Risposta
Il termine chiusura deriva dal fatto che un pezzo di codice (blocco, funzione) può avere variabili libere che sono chiuso (cioè vincolato a un valore) dallambiente in cui è definito il blocco di codice.
Prendiamo ad esempio la definizione della funzione Scala :
def addConstant(v: Int): Int = v + k
Nel corpo della funzione sono presenti due nomi (variabili) v
e k
che indica due valori interi. Il nome v
è vincolato perché è dichiarato come argomento della funzione addConstant
(osservando la dichiarazione della funzione sappiamo che v
verrà assegnato un valore quando la funzione viene invocata). Il nome k
è gratuito rispetto alla funzione addConstant
perché la funzione non contiene alcun indizio sul valore k
è vincolato a (e come).
Per valutare una chiamata come:
val n = addConstant(10)
dobbiamo assegnare k
un valore, che può verificarsi solo se il nome k
è definito nel contesto in cui addConstant
è definito. Ad esempio:
def increaseAll(values: List[Int]): List[Int] = { val k = 2 def addConstant(v: Int): Int = v + k values.map(addConstant) }
Ora che abbiamo definito addConstant
in un contesto in cui k
è definito, addConstant
è diventato una chiusura perché tutti le sue variabili libere sono ora chiuse (legate a un valore): addConstant
può essere invocato e passato in giro come se fosse una funzione. Tieni presente che la variabile libera k
è associata a un valore quando la chiusura è definita , mentre la variabile argomento v
è vincolata quando la chiusura è invocata .
Quindi una chiusura è fondamentalmente una funzione o un blocco di codice che può accedere a valori non locali attraverso le sue variabili libere dopo che queste sono state vincolate dal contesto.
In molte lingue, se tu utilizza una chiusura solo una volta che puoi renderla anonima , ad es.
def increaseAll(values: List[Int]): List[Int] = { val k = 2 values.map(v => v + k) }
Nota che una funzione senza variabili libere è un caso speciale di chiusura (con un insieme vuoto di variabili libere). Analogamente, una funzione anonima è un caso speciale di chiusura anonima , ovvero una funzione anonima è una chiusura anonima senza variabili libere.
Commenti
- Questo scherza bene con formule chiuse e aperte in logica. Grazie per la tua risposta.
- @RainDoctor: le variabili libere sono definite nelle formule logiche e nelle espressioni lambda calculus in modo simile: il lambda in unespressione lambda funziona come un quantificatore nelle formule logiche rispetto a variabili libere / vincolate .
Risposta
Una semplice spiegazione in JavaScript:
var closure_example = function() { var closure = 0; // after first iteration the value will not be erased from the memory // because it is bound with the returned alertValue function. return { alertValue : function() { closure++; alert(closure); } }; }; closure_example();
alert(closure)
utilizzerà il valore creato in precedenza di closure
. Lo spazio dei nomi della alertValue
funzione “restituito verrà connesso allo spazio dei nomi in cui risiede la variabile closure
. Quando elimini lintera funzione, il il valore della variabile closure
verrà eliminato, ma fino ad allora la funzione alertValue
sarà sempre in grado di leggere / scrivere il valore della variabile closure
.
Se esegui questo codice, la prima iterazione assegnerà un valore 0 alla variabile closure
e riscrivi la funzione in:
var closure_example = function(){ alertValue : function(){ closure++; alert(closure); } }
E poiché alertValue
necessita della variabile locale closure
per eseguire la funzione, si lega al valore della variabile locale assegnata in precedenza closure
.
E ora ogni volta che chiami closure_example
, scriverà il valore incrementato della variabile closure
perché alert(closure)
è associato.
closure_example.alertValue()//alerts value 1 closure_example.alertValue()//alerts value 2 closure_example.alertValue()//alerts value 3 //etc.
Commenti
- grazie, non lho fatto ‘ t prova il codice =) ora sembra tutto a posto.
Risposta
Una “chiusura” è , in sostanza, uno stato locale e un po di codice, combinati in un pacchetto. In genere, lo stato locale proviene da un ambito circostante (lessicale) e il codice è (essenzialmente) una funzione interna che viene quindi restituita allesterno. La chiusura è quindi una combinazione delle variabili catturate che vede la funzione interna e il codice della funzione interna.
È una di quelle cose che, sfortunatamente, è un po difficile da spiegare, a causa di non familiare.
Unanalogia che ho utilizzato con successo in passato è stata “immagina di avere qualcosa che chiamiamo” il libro “, nella chiusura della stanza,” il libro “è quella copia lì, nellangolo , di TAOCP, ma sulla chiusura del tavolo, è quella copia di un libro di Dresden Files. Quindi, a seconda della chiusura in cui ti trovi, il codice “dammi il libro” fa sì che accadano cose diverse “.
Commenti
- Hai dimenticato questo: en.wikipedia.org/wiki/Closure_(computer_programming) nella tua risposta.
- No, ho consapevolmente scelto di non chiudere quella pagina.
- ” Stato e funzione. “: una funzione C con una variabile locale
static
può essere considerata una chiusura? Le chiusure in Haskell coinvolge lo stato? - @Giorgio Closures in Haskell chiude (credo) sugli argomenti nellambito lessicale in cui ‘ è stato ridefinito, quindi, io ‘ dì ” sì ” (anche se nella migliore delle ipotesi non ho familiarità con Haskell). La funzione AC con una variabile statica è, nella migliore delle ipotesi, una chiusura molto limitata (vuoi davvero essere in grado di creare più chiusure da una singola funzione, con una variabile locale
static
, devi esattamente uno). - Ho posto questa domanda apposta perché penso che una funzione C con una variabile statica non sia una chiusura: la variabile statica è definita localmente e conosciuta solo allinterno della chiusura, non accede lambiente. Inoltre, non sono sicuro al 100%, ma formulerei la tua dichiarazione al contrario: usi il meccanismo di chiusura per creare diverse funzioni (una funzione è una definizione di chiusura + un legame per le sue variabili libere).
Risposta
È difficile definire cosa sia la chiusura senza definire il concetto di “stato”.
Fondamentalmente , in un linguaggio con un ambito lessicale completo che tratta le funzioni come valori di prima classe, accade qualcosa di speciale. Se dovessi fare qualcosa del tipo:
function foo(x) return x end x = foo
La variabile x
non fa riferimento solo a function foo()
ma fa anche riferimento allo stato foo
è stato lasciato lultima volta che è stato restituito. La vera magia accade quando foo
ha altre funzioni ulteriormente definite nel suo ambito; è come il suo mini-ambiente (proprio come “normalmente” definiamo le funzioni in un ambiente globale).
Funzionalmente può risolvere molti degli stessi problemi del C ++ (C?) “s” statica “parola chiave, che mantiene lo stato di una variabile locale durante più chiamate di funzione; tuttavia è più come applicare lo stesso principio (variabile statica) a una funzione, poiché le funzioni sono valori di prima classe; la chiusura aggiunge il supporto per il salvataggio dello stato dellintera funzione (niente a che fare con le funzioni statiche di C ++).
Trattare le funzioni come valori di prima classe e aggiungere il supporto per le chiusure significa anche che puoi avere più di unistanza della stessa funzione in memoria (simile alle classi). Ciò significa che puoi riutilizzare il stesso codice senza dover reimpostare lo stato della funzione, come è richiesto quando si ha a che fare con variabili statiche C ++ allinterno di una funzione (potrebbe essere sbagliato su questo?).
Ecco alcuni test del supporto di chiusura di Lua .
--Closure testing --By Trae Barlow -- function myclosure() print(pvalue)--nil local pvalue = pvalue or 10 return function() pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed) print(pvalue) pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls) return pvalue end end x = myclosure() --x now references anonymous function inside myclosure() x()--nil, 20 x() --21, 31 x() --32, 42 --43, 53 -- if we iterated x() again
risultati:
nil 20 31 42
Può diventare complicato e probabilmente varia da lingua a lingua, ma sembra in Lua che ogni volta che una funzione viene eseguita, il suo stato viene ripristinato. Dico questo perché i risultati del codice sopra sarebbero diversi se stessimo accedendo al funzione / stato direttamente (invece che tramite la funzione anonima restituisce), poiché pvalue
verrebbe reimpostato su 10; ma se accediamo allo stato di myclosure tramite x (la funzione anonima) puoi vedere che pvalue
è vivo e vegeto da qualche parte nella memoria. Sospetto che ci sia qualcosa di più, forse qualcuno può spiegare meglio la natura dellimplementazione.
PS: Non conosco un tocco di C ++ 11 (a parte quello che è nelle versioni precedenti) quindi tieni presente che questo non è un confronto tra le chiusure in C ++ 11 e Lua. Inoltre, tutte le “linee tracciate” da Lua a C ++ sono somiglianze poiché le variabili statiche e le chiusure non sono uguali al 100%; anche se a volte vengono utilizzati per risolvere problemi simili.
La cosa di cui non sono sicuro è, nellesempio di codice sopra, se la funzione anonima o la funzione di ordine superiore è considerata la chiusura?
Risposta
Una chiusura è una funzione a cui è associato uno stato:
In perl crei chiusure come questa:
#!/usr/bin/perl # This function creates a closure. sub getHelloPrint { # Bind state for the function we are returning. my ($first) = @_;a # The function returned will have access to the variable $first return sub { my ($second) = @_; print "$first $second\n"; }; } my $hw = getHelloPrint("Hello"); my $gw = getHelloPrint("Goodby"); &$hw("World"); // Print Hello World &$gw("World"); // PRint Goodby World
Se guardiamo alla nuova funzionalità fornita con C ++.
Ti consente anche di associare lo stato corrente alloggetto:
#include <string> #include <iostream> #include <functional> std::function<void(std::string const&)> getLambda(std::string const& first) { // Here we bind `first` to the function // The second parameter will be passed when we call the function return [first](std::string const& second) -> void { std::cout << first << " " << second << "\n"; }; } int main(int argc, char* argv[]) { auto hw = getLambda("Hello"); auto gw = getLambda("GoodBye"); hw("World"); gw("World"); }
Risposta
Consideriamo una semplice funzione:
function f1(x) { // ... something }
Questa funzione è chiamata funzione di primo livello perché “non è nidificata in nessunaltra funzione. Ogni La funzione JavaScript associa a se stessa un elenco di oggetti denominato ” Scope Chain “. Questa catena di ambito è un elenco ordinato di oggetti E tutti questi oggetti definiscono alcune variabili.
Nelle funzioni di primo livello, la catena dellambito consiste in un singolo oggetto, loggetto globale. Ad esempio, la funzione f1
di cui sopra ha una catena di ambito che contiene un singolo oggetto che definisce tutte le variabili globali. (nota che il termine “oggetto” qui non significa oggetto JavaScript, è solo un oggetto definito dallimplementazione che funge da contenitore di variabili, in cui JavaScript può “cercare” variabili.)
Quando questo viene richiamata, JavaScript crea qualcosa chiamato “Activation object” e lo colloca allinizio della catena dellambito. Questo object contiene tutte le variabili locali (ad esempio x
qui). Quindi ora abbiamo due oggetti nella catena dellambito: il primo è loggetto di attivazione e al di sotto di esso è loggetto globale.
Nota molto attentamente che i due oggetti vengono inseriti nella catena dellambito in momenti DIVERSI. Loggetto globale viene inserito quando la funzione è definita (ovvero, quando JavaScript ha analizzato la funzione e creato loggetto funzione), e il loggetto di attivazione entra quando la funzione viene invocata.
Quindi, ora sappiamo questo:
- Ogni funzione ha una catena di ambito ad essa associata
- Quandola funzione è definita (quando viene creato loggetto funzione), JavaScript salva una catena di ambito con quella funzione
- Per le funzioni di primo livello, la catena di ambito contiene solo loggetto globale al momento della definizione della funzione e aggiunge un ulteriore oggetto di attivazione in primo piano al momento dellinvocazione
La situazione diventa interessante quando si tratta di funzioni annidate. Quindi, creiamone uno:
function f1(x) { function f2(y) { // ... something } }
Quando f1
viene definito, otteniamo una catena di ambito che contiene solo loggetto globale.
Ora, quando f1
viene chiamato, la catena dellambito di f1
ottiene loggetto di attivazione. Questo oggetto di attivazione contiene la variabile x
e la variabile f2
che è una funzione. Tieni presente che f2
si sta definendo.Quindi, a questo punto, JavaScript salva anche una nuova catena di ambito per f2
. La catena dellambito salvata per questa funzione interna è lattuale catena dellambito attiva. Lambito corrente la catena in effetti è quella di f1
“s. Quindi la catena dellambito di f2
” è f1
“s corrente catena di ambito – che contiene loggetto di attivazione di f1
e loggetto globale.
Quando viene chiamato f2
, “ottiene” loggetto di attivazione che contiene y
, aggiunto alla sua catena di ambito che contiene già loggetto di attivazione di f1
e loggetto globale.
Se ci fosse unaltra funzione nidificata definita allinterno di f2
, la sua catena di ambito conterrebbe tre oggetti al momento della definizione (2 oggetti di attivazione di due funzioni esterne e loggetto globale) e 4 al momento della chiamata.
Quindi, ora siamo sotto tand come funziona la catena dellambito, ma non abbiamo ancora parlato di chiusure.
La combinazione di un oggetto funzione e di uno scopo (un insieme di associazioni di variabili) in cui le variabili della funzione vengono risolte è chiamata chiusura nella letteratura informatica – JavaScript the definitive guide di David Flanagan
La maggior parte delle funzioni vengono invocate utilizzando la stessa catena di ambito che era in vigore quando la funzione è stata definita, e non importa che sia coinvolta una chiusura. Le chiusure diventano interessanti quando vengono richiamate in una catena di ambito diversa da quella che era in vigore quando sono state definite. Ciò accade più comunemente quando un oggetto funzione nidificato è restituito dalla funzione allinterno della quale è stato definito.
Quando la funzione ritorna, quelloggetto di attivazione viene rimosso dalla catena dellambito. Se non ci fossero funzioni annidate, non ci sono più riferimenti alloggetto di attivazione e viene raccolto in garbage collection. Se sono state definite funzioni annidate, ciascuna di queste funzioni ha un riferimento alla catena dellambito e tale catena dellambito si riferisce alloggetto di attivazione.
Se tali oggetti funzioni annidate sono rimasti allinterno della loro funzione esterna, tuttavia, quindi loro stessi verranno raccolti nella spazzatura, insieme alloggetto di attivazione a cui si riferivano. Ma se la funzione definisce una funzione annidata e la restituisce o la memorizza in una proprietà da qualche parte, ci sarà un riferimento esterno alla funzione annidata. Non verrà raccolto in Garbage Collection e nemmeno loggetto di attivazione a cui si riferisce non verrà raccolto in Garbage Collection.
Nel nostro esempio precedente, non restituiamo f2
da f1
, quindi, quando viene restituita una chiamata a f1
, il suo oggetto di attivazione verrà rimosso dalla sua catena di ambito e verrà raccolta in modo indesiderato. Ma se avessimo qualcosa del genere:
function f1(x) { function f2(y) { // ... something } return f2; }
Qui, il f2
restituito avrà una catena di ambito che contengono loggetto di attivazione di f1
, e quindi non sarà “garbage collector”. A questo punto, se chiamiamo f2
, sarà in grado di accedere alla f1
“s variabile x
anche se “siamo fuori f1
.
Quindi possiamo vedere che una funzione mantiene la sua catena di ambito con essa e con la catena di ambito vengono tutti gli oggetti di attivazione delle funzioni esterne. Questa è lessenza della chiusura. Diciamo che le funzioni in JavaScript sono “con ambito lessicale” , il che significa che salvano lambito che era attivo quando sono stati definiti in contrasto con lambito che era attivo quando sono stati chiamati.
Esistono numerose tecniche di programmazione potenti che implicano chiusure come lapprossimazione di variabili private , programmazione guidata dagli eventi, applicazione parziale , ecc.
Tieni inoltre presente che tutto ciò si applica a tutti quei linguaggi che supportano le chiusure. Ad esempio PHP (5.3+), Python, Ruby, ecc.
Risposta
Una chiusura è unottimizzazione del compilatore (nota anche come zucchero sintattico?). Alcune persone lo hanno definito anche Poor Man “s Object .
Vedi la risposta di Eric Lippert : (estratto sotto)
Il compilatore genererà un codice come questo:
private class Locals { public int count; public void Anonymous() { this.count++; } } public Action Counter() { Locals locals = new Locals(); locals.count = 0; Action counter = new Action(locals.Anonymous); return counter; }
Ha senso?
Inoltre, hai chiesto dei confronti. VB e JScript creano entrambi le chiusure più o meno allo stesso modo.
Commenti
- Questa risposta è in CW perché ‘ non merito punti per Eric ‘ è unottima risposta.Per favore votalo come meglio credi. HTH
- -1: la tua spiegazione è troppo radice in C #. La chiusura è usata in molte lingue ed è molto più di uno zucchero sintattico in queste lingue e comprende sia la funzione che lo stato.
- No, una chiusura non è né solo un ” ottimizzazione del compilatore ” né zucchero sintattico. -1