[Functional-programming] Cos'è una "chiusura"?


Answers

Darò un esempio (in JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

Ciò che questa funzione, makeCounter, fa è che restituisce una funzione, che abbiamo chiamato x, che verrà conteggiata una per volta ogni volta che viene chiamata. Dato che non forniamo alcun parametro per x, deve in qualche modo ricordare il conteggio. Sa dove trovarlo in base a ciò che viene chiamato scoping lessicale - deve cercare nel punto in cui è definito per trovare il valore. Questo valore "nascosto" è ciò che viene chiamato una chiusura.

Ecco di nuovo il mio esempio di curriculum:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);

add3(4); returns 7

Quello che puoi vedere è che quando chiami add con il parametro a (che è 3), quel valore è contenuto nella chiusura della funzione restituita che stiamo definendo come add3. In questo modo, quando chiamiamo add3, sa dove trovare il valore per eseguire l'aggiunta.

Question

Ho fatto una domanda su Currying e chiusure sono state citate. Cos'è una chiusura? Come si relaziona al curry?




In una situazione normale, le variabili sono vincolate dalla regola dell'ambito: le variabili locali funzionano solo all'interno della funzione definita. La chiusura è un modo per infrangere temporaneamente questa regola per praticità.

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

nel codice precedente, lambda(|n| a_thing * n} è la chiusura perché a_thing viene indicato da lambda (un creatore di funzioni anonime).

Ora, se si inserisce la funzione anonima risultante in una variabile di funzione.

foo = n_times(4)

foo infrangerà la normale regola dell'ambito e inizierà a usare 4 internamente.

foo.call(3)

restituisce 12.




Ecco un altro esempio di vita reale e l'utilizzo di un linguaggio di scripting popolare nei giochi: Lua. Ho dovuto cambiare leggermente il modo in cui una funzione di libreria ha funzionato per evitare un problema con stdin non disponibile.

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

Il valore di old_dofile scompare quando questo blocco di codice termina è scope (perché è locale), tuttavia il valore è stato incluso in una chiusura, quindi la nuova funzione dofile ridefinita può accedervi, o meglio una copia memorizzata insieme alla funzione come 'sopravvalutare'.




Prima di tutto, contrariamente a ciò che la maggior parte delle persone qui vi dice, la chiusura non è una funzione ! Allora, cos'è?
È un insieme di simboli definiti nel "contesto circostante" di una funzione (noto come ambiente ) che ne fa un'espressione CHIUSA (ovvero un'espressione in cui ogni simbolo è definito e ha un valore, quindi può essere valutato).

Ad esempio, quando hai una funzione JavaScript:

function closed(x) {
  return x + 3;
}

è un'espressione chiusa perché tutti i simboli presenti in esso sono definiti in esso (i loro significati sono chiari), quindi puoi valutarlo. In altre parole, è autonomo .

Ma se hai una funzione come questa:

function open(x) {
  return x*y + 3;
}

è un'espressione aperta perché ci sono simboli che non sono stati definiti in essa. Vale a dire, y . Quando osserviamo questa funzione, non possiamo dire cosa sia e cosa significhi, non sappiamo il suo valore, quindi non possiamo valutare questa espressione. Vale a dire, non possiamo chiamare questa funzione finché non diciamo cosa si suppone significhi in essa. Questo y è chiamato una variabile libera .

Ciò richiede una definizione, ma questa definizione non fa parte della funzione - è definita da qualche altra parte, nel suo "contesto circostante" (noto anche come ambiente ). Almeno è quello che speriamo: P

Ad esempio, potrebbe essere definito globalmente:

var y = 7;

function open(x) {
  return x*y + 3;
}

Oppure potrebbe essere definito in una funzione che lo avvolge:

var global = 2;

function wrapper(y) {
   var w = "unused";

   return function(x) {
     return x*y + 3;
   }

}

La parte dell'ambiente che conferisce alle variabili libere in un'espressione il loro significato è la chiusura . Si chiama così, perché trasforma un'espressione aperta in una chiusa , fornendo queste definizioni mancanti per tutte le sue variabili libere , in modo che possiamo valutarla.

Nell'esempio sopra, la funzione interna (che non abbiamo dato un nome perché non ne abbiamo avuto bisogno) è un'espressione aperta perché la variabile y in essa è libera - la sua definizione è al di fuori della funzione, nella funzione che avvolge esso. L' ambiente per quella funzione anonima è l'insieme di variabili:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Ora, la chiusura è quella parte di questo ambiente che chiude la funzione interna fornendo le definizioni per tutte le sue variabili libere . Nel nostro caso, l'unica variabile libera nella funzione interna era y , quindi la chiusura di tale funzione è questo sottoinsieme del suo ambiente:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

Gli altri due simboli definiti nell'ambiente non fanno parte della chiusura di tale funzione, poiché non richiede che vengano eseguiti. Non sono necessari per chiuderlo .

Maggiori informazioni sulla teoria alla base qui: https://.com/a/36878651/434562

Vale la pena notare che nell'esempio sopra, la funzione wrapper restituisce la sua funzione interna come valore. Il momento in cui chiamiamo questa funzione può essere remoto nel tempo dal momento in cui la funzione è stata definita (o creata). In particolare, la sua funzione di wrapping non è più in esecuzione e i suoi parametri che erano nello stack delle chiamate non sono più lì: P Questo crea un problema, perché la funzione interna ha bisogno che y sia lì quando viene richiamata! In altre parole, richiede che le variabili dalla sua chiusura sopravvivano in qualche modo alla funzione wrapper e siano lì quando necessario. Pertanto, la funzione interna deve fare un'istantanea di queste variabili che ne rendono la chiusura e memorizzarle in un luogo sicuro per un uso successivo. (Da qualche parte al di fuori della pila di chiamate.)

Ed è per questo che le persone spesso confondono il termine chiusura come quel particolare tipo di funzione che può fare tali istantanee delle variabili esterne che usano, o la struttura dati utilizzata per memorizzare queste variabili per dopo. Ma spero che tu capisca ora che non sono la chiusura stessa - sono solo modi per attuare chiusure in un linguaggio di programmazione, o meccanismi linguistici che consentono alle variabili dalla chiusura della funzione di essere lì quando necessario. Ci sono un sacco di idee sbagliate riguardo alle chiusure che (inutilmente) rendono questo argomento molto più confuso e complicato di quanto sia in realtà.




Una chiusura è una funzione che può riferirsi allo stato in un'altra funzione. Ad esempio, in Python, questo usa la chiusura "inner":

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1



Se provieni dal mondo Java, puoi confrontare una chiusura con una funzione membro di una classe. Guarda questo esempio

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

La funzione g è una chiusura: g chiude a in. Quindi g può essere confrontato con una funzione membro, a può essere confrontato con un campo classe, e la funzione f con una classe.




In breve, il puntatore a funzione è solo un puntatore a una posizione nella base del codice del programma (come il contatore del programma). Mentre Closure = Function pointer + Stack frame .

.