Di tanto in tanto vedo le "chiusure" menzionate, e ho provato a cercarlo, ma Wiki non fornisce una spiegazione che capisco. Qualcuno potrebbe aiutarmi qui?
Di tanto in tanto vedo le "chiusure" menzionate, e ho provato a cercarlo, ma Wiki non fornisce una spiegazione che capisco. Qualcuno potrebbe aiutarmi qui?
Risposte:
(Dichiarazione di non responsabilità: questa è una spiegazione di base; per quanto riguarda la definizione, sto semplificando un po ')
Il modo più semplice di pensare a una chiusura è una funzione che può essere archiviata come variabile (definita "funzione di prima classe"), che ha una capacità speciale di accedere ad altre variabili locali nell'ambito 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. Potete vedere che entrambi fanno riferimento alla variabile booleana black
, ma quella variabile è assegnata al di fuori della funzione. Poiché black
è locale nell'ambito in cui è stata definita la funzione , il puntatore a questa variabile viene conservato.
Se lo metti in una pagina HTML:
Ciò dimostra che entrambi hanno accesso allo stesso black
e possono essere utilizzati per memorizzare lo stato senza alcun oggetto wrapper.
La chiamata a setKeyPress
è per dimostrare come una funzione può essere passata proprio come qualsiasi variabile. L' ambito conservato nella chiusura è ancora quello in cui è stata definita la funzione.
Le chiusure vengono comunemente utilizzate come gestori di eventi, soprattutto in JavaScript e ActionScript. Un buon uso delle chiusure ti aiuterà a associare implicitamente le variabili ai gestori di eventi senza dover creare un wrapper di oggetti. Tuttavia, un uso non attento porterà a perdite di memoria (come quando un gestore di eventi inutilizzato ma conservato è l'unica cosa da conservare su oggetti di grandi dimensioni in memoria, in particolare oggetti DOM, impedendo la garbage collection).
1: In realtà, tutte le funzioni in JavaScript sono chiusure.
black
è dichiarato all'interno di una funzione, non verrebbe distrutto quando lo stack si svolgerà ...?
black
è dichiarato all'interno di una funzione, non verrebbe distrutto". Ricorda anche che se dichiari un oggetto in una funzione e poi lo assegni a una variabile che vive da qualche altra parte, quell'oggetto viene preservato perché ci sono altri riferimenti ad esso.
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 è da dove vengono.
Nella programmazione orientata agli oggetti, si dichiara una classe di oggetti definendo le sue variabili membro e i suoi metodi (funzioni membro) in anticipo, quindi si creano istanze di quella classe. Ogni istanza viene fornita con una copia dei dati dei membri, inizializzata dal costruttore. È quindi possibile disporre di una variabile di un tipo di oggetto e passarla come parte di dati, poiché l'attenzione si concentra sulla sua natura di dati.
In una chiusura, d'altra parte, l'oggetto non viene definito in anticipo come una classe di oggetti o istanziato attraverso una chiamata del costruttore nel codice. Invece, scrivi la chiusura come funzione all'interno di un'altra funzione. La chiusura può fare riferimento a una qualsiasi delle variabili locali della funzione esterna e il compilatore lo rileva e sposta queste variabili dallo spazio di stack della funzione esterna alla dichiarazione di oggetti nascosti della chiusura. Quindi hai una variabile di un tipo di chiusura e anche se è fondamentalmente un oggetto sotto il cofano, lo fai passare come riferimento di funzione, perché l'attenzione è sulla sua natura come funzione.
Il termine chiusura deriva dal fatto che un pezzo di codice (blocco, funzione) può avere variabili libere che sono chiuse (cioè legate a un valore) dall'ambiente 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 ci sono due nomi (variabili) v
che k
indicano due valori interi. Il nome v
è associato perché viene dichiarato come argomento della funzione addConstant
(osservando la dichiarazione della funzione sappiamo che v
verrà assegnato un valore quando viene invocata la funzione). Il nome k
è libero per la funzione addConstant
perché la funzione non contiene alcun indizio su quale valore k
sia associato (e come).
Per valutare una chiamata come:
val n = addConstant(10)
dobbiamo assegnare k
un valore, che può accadere solo se il nome k
è definito nel contesto in cui addConstant
è definito. Per 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é tutte le sue variabili libere sono ora chiuse (legate a un valore): addConstant
possono essere invocate e passate in giro come se fossero una funzione. Si noti che la variabile libera k
è associata a un valore quando viene definita la chiusura , mentre la variabile argomento v
viene associata quando viene invocata la chiusura .
Quindi una chiusura è sostanzialmente 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 si utilizza una chiusura solo una volta, è possibile renderla anonima , ad es
def increaseAll(values: List[Int]): List[Int] =
{
val k = 2
values.map(v => v + k)
}
Si noti che una funzione senza variabili libere è un caso speciale di chiusura (con un set vuoto di variabili libere). Analogamente, una funzione anonima è un caso speciale di chiusura anonima , ovvero una funzione anonima è una chiusura anonima senza variabili libere.
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 precedentemente creato di closure
. Lo alertValue
spazio dei nomi della funzione restituita sarà collegato allo spazio dei nomi in cui closure
risiede la variabile. Quando si elimina l'intera funzione, il valore della closure
variabile verrà eliminato, ma fino ad allora, la alertValue
funzione sarà sempre in grado di leggere / scrivere il valore della variabile closure
.
Se si esegue questo codice, la prima iterazione assegnerà un valore 0 alla closure
variabile e riscriverà la funzione a:
var closure_example = function(){
alertValue : function(){
closure++;
alert(closure);
}
}
E poiché alertValue
necessita della variabile locale closure
per eseguire la funzione, si lega con il valore della variabile locale precedentemente assegnata closure
.
E ora ogni volta che chiami la closure_example
funzione, scriverà il valore incrementato della closure
variabile perché alert(closure)
è associato.
closure_example.alertValue()//alerts value 1
closure_example.alertValue()//alerts value 2
closure_example.alertValue()//alerts value 3
//etc.
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 all'esterno. 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 della mancanza di familiarità.
Un'analogia che ho usato con successo in passato era "immagina di avere qualcosa che chiamiamo" il libro ", nella chiusura della stanza," il libro "è quella copia lì, nell'angolo, di TAOCP, ma sulla chiusura del tavolo , è quella copia di un libro dei file di Dresda. Quindi, a seconda della chiusura in cui ti trovi, il codice "dammi il libro" provoca diverse cose. "
static
variabile locale può essere considerata una chiusura? Le chiusure in Haskell coinvolgono lo stato?
static
variabile locale, ne hai esattamente una).
È difficile definire cosa sia la chiusura senza definire il concetto di "stato".
Fondamentalmente, in un linguaggio con ambito lessicale completo che considera le funzioni come valori di prima classe, succede qualcosa di speciale. Se dovessi fare qualcosa del genere:
function foo(x)
return x
end
x = foo
La variabile x
non solo fa riferimento function foo()
ma fa anche riferimento allo stato è foo
stato lasciato l'ultima volta che è tornato. La vera magia si verifica quando foo
altre funzioni sono ulteriormente definite nel suo ambito; è come il suo mini-ambiente (così come "normalmente" definiamo le funzioni in un ambiente globale).
Funzionalmente può risolvere molti degli stessi problemi della parola chiave 'statica' del C ++ (C?), Che mantiene lo stato di una variabile locale attraverso 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 lo stato dell'intera funzione da salvare (nulla a che fare con le funzioni statiche di C ++).
Considerare le funzioni come valori di prima classe e aggiungere il supporto per le chiusure significa anche che è possibile avere più di un'istanza della stessa funzione in memoria (simile alle classi). Ciò significa che è possibile riutilizzare lo stesso codice senza dover reimpostare lo stato della funzione, come richiesto quando si ha a che fare con variabili statiche C ++ all'interno di una funzione (potrebbe esserci di sbagliato in 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
i risultati:
nil
20
31
42
Può diventare complicato, e probabilmente varia da lingua a lingua, ma in Lua sembra che ogni volta che viene eseguita una funzione, il suo stato viene ripristinato. Dico questo perché i risultati del codice sopra sarebbero diversi se accedessimo direttamente alla myclosure
funzione / stato (invece che attraverso la funzione anonima che ritorna), poiché pvalue
verremmo ripristinati a 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. Ho il sospetto che ci sia qualcosa in più, forse qualcuno può spiegare meglio la natura dell'implementazione.
PS: Non conosco una leccata di C ++ 11 (diversa da quella delle versioni precedenti), quindi nota che questo non è un confronto tra le chiusure in C ++ 11 e Lua. Inoltre, tutte le "linee tracciate" da Lua a C ++ sono somiglianze in quanto 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 è, nell'esempio di codice sopra, se la funzione anonima o la funzione di ordine superiore è considerata la chiusura?
Una chiusura è una funzione che ha stato associato:
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 esaminiamo le nuove funzionalità fornite con C ++.
Inoltre, consente di associare lo stato corrente all'oggetto:
#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");
}
Consideriamo una semplice funzione:
function f1(x) {
// ... something
}
Questa funzione è chiamata funzione di livello superiore perché non è nidificata in nessun'altra funzione. Ogni funzione JavaScript associa a se stessa un elenco di oggetti chiamato "Scope Chain" . Questa catena di ambiti è un elenco ordinato di oggetti. Ognuno di questi oggetti definisce alcune variabili.
Nelle funzioni di livello superiore, la catena dell'ambito è costituita da un singolo oggetto, l'oggetto globale. Ad esempio, la funzione f1
sopra ha una catena di ambito che contiene un singolo oggetto che definisce tutte le variabili globali. (nota che qui il termine "oggetto" non significa oggetto JavaScript, è solo un oggetto definito dall'implementazione che funge da contenitore di variabili, in cui JavaScript può "cercare" le variabili.)
Quando viene invocata questa funzione, JavaScript crea qualcosa chiamato "Oggetto di attivazione" e lo mette in cima alla catena dell'ambito. Questo oggetto contiene tutte le variabili locali (ad esempio x
qui). Quindi ora abbiamo due oggetti nella catena dell'ambito: il primo è l'oggetto di attivazione e sotto di esso è l'oggetto globale.
Notare con molta attenzione che i due oggetti vengono inseriti nella catena dell'oscilloscopio in tempi DIVERSI. L'oggetto globale viene inserito quando viene definita la funzione (ovvero quando JavaScript ha analizzato la funzione e creato l'oggetto funzione) e l'oggetto di attivazione entra quando viene invocata la funzione.
Quindi ora sappiamo questo:
La situazione diventa interessante quando abbiamo a che fare con funzioni nidificate. Quindi, creiamo uno:
function f1(x) {
function f2(y) {
// ... something
}
}
Quando f1
viene definito otteniamo una catena di ambito per esso contenente solo l'oggetto globale.
Ora quando f1
viene chiamato, la catena dell'ambito f1
ottiene l'oggetto di attivazione. Questo oggetto di attivazione contiene la variabile x
e la variabile f2
che è una funzione. E, nota che f2
si sta definendo. Quindi, a questo punto, JavaScript salva anche una nuova catena di ambito per f2
. La catena di portata salvata per questa funzione interna è la catena di portata corrente in vigore. L'attuale catena di portata in effetti è quella di f1
's. Quindi f2
la catena di portata è f1
la catena di portata corrente - che contiene l'oggetto di attivazione f1
e l'oggetto globale.
Quando f2
viene chiamato, ottiene il proprio oggetto di attivazione contenente y
, aggiunto alla sua catena di portata che contiene già l'oggetto di attivazione f1
e l'oggetto globale.
Se all'interno fosse definita un'altra funzione nidificata f2
, la catena dell'ambito conterrebbe tre oggetti al momento della definizione (2 oggetti di attivazione di due funzioni esterne e l'oggetto globale) e 4 al momento dell'invocazione.
Quindi, ora capiamo come funziona la catena dell'ambito ma non abbiamo ancora parlato di chiusure.
La combinazione di un oggetto funzione e un ambito (un insieme di associazioni di variabili) in cui vengono risolte le variabili della funzione è chiamata chiusura nella letteratura informatica - JavaScript la guida definitiva di David Flanagan
La maggior parte delle funzioni viene invocata utilizzando la stessa catena di ambito che era in vigore al momento della definizione della funzione e non importa che sia coinvolta una chiusura. Le chiusure diventano interessanti quando vengono invocate in una catena di ambito diversa da quella che era in vigore al momento della definizione. Ciò accade più comunemente quando un oggetto funzione nidificato viene restituito dalla funzione in cui è stato definito.
Quando la funzione ritorna, quell'oggetto di attivazione viene rimosso dalla catena dell'ambito. Se non c'erano funzioni nidificate, non ci sono più riferimenti all'oggetto di attivazione e viene raccolta spazzatura. Se sono state definite funzioni nidificate, ciascuna di queste funzioni ha un riferimento alla catena dell'ambito e tale catena dell'ambito si riferisce all'oggetto di attivazione.
Se quegli oggetti con funzioni nidificate sono rimasti all'interno della loro funzione esterna, tuttavia, essi stessi saranno raccolti in modo inutile, insieme all'oggetto di attivazione a cui si riferivano. Ma se la funzione definisce una funzione nidificata e la restituisce o la memorizza in una proprietà da qualche parte, allora ci sarà un riferimento esterno alla funzione nidificata. Non sarà garbage collection e nemmeno l'oggetto di attivazione a cui fa riferimento non sarà garbage collection.
Nel nostro esempio precedente, non torniamo f2
da f1
, quindi, quando viene f1
restituita una chiamata , il suo oggetto di attivazione verrà rimosso dalla sua catena di portata e i rifiuti raccolti. Ma se avessimo qualcosa del genere:
function f1(x) {
function f2(y) {
// ... something
}
return f2;
}
Qui, il ritorno f2
avrà una catena di ambito che conterrà l'oggetto di attivazione di f1
, e quindi non verrà raccolto. A questo punto, se chiamiamo f2
, sarà in grado di accedere alla f1
variabile x
anche se siamo fuori f1
.
Quindi possiamo vedere che una funzione mantiene la sua catena di portata con essa e con la catena di portata arrivano tutti gli oggetti di attivazione delle funzioni esterne. Questa è l'essenza della chiusura. Diciamo che le funzioni in JavaScript sono "con ambito lessicale" , il che significa che salvano l'ambito che era attivo quando sono state definite rispetto all'ambito che era attivo quando sono state chiamate.
Esistono numerose potenti tecniche di programmazione che comportano chiusure quali approssimazioni di variabili private, programmazione guidata da eventi, applicazione parziale , ecc.
Si noti inoltre che tutto ciò si applica a tutte quelle lingue che supportano le chiusure. Ad esempio PHP (5.3+), Python, Ruby, ecc.
Una chiusura è un'ottimizzazione del compilatore (aka zucchero sintattico?). Alcune persone si sono riferite a questo anche come Poor Man's Object .
Vedi la risposta di Eric Lippert : (estratto sotto)
Il compilatore genererà il codice in questo modo:
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 confronti. VB e JScript creano entrambi chiusure praticamente allo stesso modo.