traduzione - wiki lambda function




Qual è la differenza tra una "chiusura" e una "lambda"? (8)

Qualcuno potrebbe spiegare? Capisco i concetti di base alla base, ma li vedo spesso usati in modo intercambiabile e mi confondo.

E ora che siamo qui, come si differenziano da una funzione normale?


È così semplice: lambda è un costrutto linguistico, cioè semplicemente sintassi per funzioni anonime; una chiusura è una tecnica per implementarlo - o qualsiasi funzione di prima classe, del resto, denominata o anonima.

Più precisamente, una chiusura è il modo in cui una funzione di prima classe è rappresentata in fase di runtime, come una coppia del suo "codice" e un "ambiente" di chiusura su tutte le variabili non locali utilizzate in quel codice. In questo modo, tali variabili sono ancora accessibili anche quando gli ambiti esterni da cui sono originati sono già stati aperti.

Sfortunatamente, ci sono molte lingue là fuori che non supportano funzioni come valori di prima classe, o che supportano solo in forma storpia. Quindi le persone usano spesso il termine "chiusura" per distinguere "la cosa reale".


C'è molta confusione intorno a lambda e chiusure, anche nelle risposte a questa domanda qui. Invece di chiedere ai programmatori casuali che hanno imparato a conoscere le chiusure dalla pratica con determinati linguaggi di programmazione o altri programmatori senza cloni, fai un viaggio verso la fonte (dove tutto è iniziato). E dal momento che lambda e chiusure provengono dal Lambda Calculus inventato da Alonzo Church negli anni '30, prima ancora che esistessero i primi computer elettronici, questa è la fonte di cui sto parlando.

Lambda Calculus è il linguaggio di programmazione più semplice al mondo. Le uniche cose che puoi fare in questo: ►

  • APPLICAZIONE: applicare un'espressione a un'altra, denotata fx .
    (Pensa a una chiamata di funzione , dove f è la funzione e x è il suo unico parametro)
  • ABSTRACTION: lega un simbolo che si verifica in un'espressione per indicare che questo simbolo è solo uno "slot", una casella vuota in attesa di essere riempita di valore, una "variabile", per così dire. Si esegue anteponendo una lettera greca λ (lambda), quindi il nome simbolico (ad esempio x ), quindi un punto . prima dell'espressione. Quindi converte l'espressione in una funzione che prevede un parametro .
    Ad esempio: λx.x+2 prende l'espressione x+2 e indica che il simbolo x in questa espressione è una variabile associata , che può essere sostituita con un valore fornito come parametro.
    Nota che la funzione definita in questo modo è anonima - non ha un nome, quindi non puoi ancora farne riferimento, ma puoi immediatamente chiamarla (ricorda l'applicazione?) Fornendogli il parametro che sta aspettando, come questo: (λx.x+2) 7 . Quindi l'espressione (in questo caso un valore letterale) 7 viene sostituita come x nella sottoespressione x+2 della lambda applicata, quindi ottieni 7+2 , che quindi si riduce a 9 secondo regole di aritmetica comuni.

Quindi abbiamo risolto uno dei misteri:
lambda è la funzione anonima dell'esempio precedente, λx.x+2 .

In diversi linguaggi di programmazione, la sintassi per l'astrazione funzionale (lambda) può differire. Ad esempio, in JavaScript appare così:

function(x) { return x+2; }

e puoi applicarlo immediatamente ad alcuni parametri come questo:

(function(x) { return x+2; })(7)

oppure puoi memorizzare questa funzione anonima (lambda) in una variabile:

var f = function(x) { return x+2; }

che gli dà effettivamente un nome f , permettendoti di fare riferimento ad esso e chiamarlo più volte dopo, ad esempio:

alert(  f(7) + f(10)  );   // should print 21 in the message box

Ma non dovevi nominarlo. Potresti chiamarlo immediatamente:

alert(  function(x) { return x+2; } (7)  );  // should print 9 in the message box

In LISP, i lambda sono fatti così:

(lambda (x) (+ x 2))

e puoi chiamare tale lambda applicandolo immediatamente a un parametro:

(  (lambda (x) (+ x 2))  7  )

OK, ora è il momento di risolvere l'altro mistero: cos'è una chiusura . Per fare ciò, parliamo di simboli ( variabili ) nelle espressioni lambda.

Come ho detto, ciò che l'astrazione lambda fa è vincolare un simbolo nella sua sottoespressione, in modo che diventi un parametro sostituibile. Tale simbolo è chiamato associato . Ma cosa succede se ci sono altri simboli nell'espressione? Ad esempio: λx.x/y+2 . In questa espressione, il simbolo x è legato dall'astrazione lambda λx. precedendolo. Ma l'altro simbolo, y , non è vincolato, è gratuito . Non sappiamo di cosa si tratta e da dove viene, quindi non sappiamo cosa significhi e quale valore rappresenti, e quindi non possiamo valutare quell'espressione fino a quando non capiremo cosa y .

In effetti, lo stesso vale per gli altri due simboli, 2 e + . È solo che siamo così familiari con questi due simboli che di solito dimentichiamo che il computer non li conosce e che dobbiamo dire loro cosa significano definendoli da qualche parte, ad esempio in una biblioteca o nella lingua stessa.

Puoi pensare ai simboli liberi come definiti altrove, al di fuori dell'espressione, nel suo "contesto circostante", che è chiamato il suo ambiente . L'ambiente potrebbe essere un'espressione più grande di questa espressione è una parte di (come ha detto Qui-Gon Jinn: "C'è sempre un pesce più grande";)), o in qualche biblioteca, o nella lingua stessa (come una primitiva ).

Questo ci consente di dividere espressioni lambda in due categorie:

  • Espressioni CHIUSE: ogni simbolo che si verifica in queste espressioni è vincolato da un'astrazione lambda. In altre parole, sono autonomi ; non richiedono alcun contesto circostante da valutare. Sono anche chiamati combinatori .
  • Espressioni OPEN: alcuni simboli in queste espressioni non sono vincolati - cioè, alcuni dei simboli che si verificano in essi sono gratuiti e richiedono alcune informazioni esterne, e quindi non possono essere valutati finché non si forniscono le definizioni di questi simboli.

È possibile CHIUDERE un'espressione lambda aperta fornendo l' ambiente , che definisce tutti questi simboli liberi vincolandoli ad alcuni valori (che possono essere numeri, stringhe, funzioni anonime, ovvero lambda, qualunque cosa ...).

E qui arriva la parte di chiusura :
La chiusura di un'espressione lambda è questa particolare serie di simboli definiti nel contesto esterno (ambiente) che danno valori ai simboli liberi in questa espressione, rendendoli non-liberi più. Trasforma un'espressione lambda aperta , che contiene ancora alcuni simboli "indefiniti" liberi, in una chiusa , che non ha più simboli liberi.

Ad esempio, se hai la seguente espressione lambda: λx.x/y+2 , il simbolo x è vincolato, mentre il simbolo y è libero, quindi l'espressione è open e non può essere valutata a meno che tu non dica cosa intendi (e lo stesso con + e 2 , che sono anche gratuiti). Ma supponiamo che tu abbia anche un ambiente come questo:

{  y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5  }

Questo ambiente fornisce le definizioni per tutti i simboli "non definiti" (liberi) dalla nostra espressione lambda ( y , + , 2 ) e diversi simboli extra ( q , w ). I simboli che dobbiamo definire sono questo sottoinsieme dell'ambiente:

{  y: 3,
+: [built-in addition],
2: [built-in number]  }

e questa è precisamente la chiusura della nostra espressione lambda:>

In altre parole, chiude un'espressione lambda aperta. Questo è il punto in cui è nata la chiusura del nome, ed è per questo che le risposte di molte persone in questa discussione non sono del tutto corrette: P

Allora perché si sbagliano? Perché così tanti di loro dicono che le chiusure sono alcune strutture dati in memoria, o alcune caratteristiche delle lingue che usano, o perché confondono chiusure con lambda? : P

Bene, i marketoid aziendali di Sun / Oracle, Microsoft, Google, ecc. Sono da biasimare, perché è quello che hanno chiamato questi costrutti nei loro linguaggi (Java, C #, Go ecc.). Chiamano spesso "chiusure" quelle che dovrebbero essere solo lambda. O chiamano "chiusure" una tecnica particolare che hanno usato per implementare lo scoping lessicale, ovvero il fatto che una funzione possa accedere alle variabili che sono state definite nel suo ambito esterno al momento della sua definizione. Spesso dicono che la funzione "racchiude" queste variabili, ovvero le cattura in una struttura dati per salvarle dall'essere distrutte dopo che la funzione esterna ha terminato l'esecuzione. Ma questo è solo post factum " etnologia del folklore" e marketing, che rende le cose solo più confuse, perché ogni venditore di lingue usa la propria terminologia.

Ed è anche peggio per il fatto che c'è sempre un po 'di verità in quello che dicono, che non ti lascia facilmente liquidare come falso: P Lasciami spiegare:

Se si desidera implementare una lingua che utilizza lambda come cittadini di prima classe, è necessario consentire loro di utilizzare simboli definiti nel contesto circostante (ovvero, per utilizzare variabili libere nei propri lambda). E questi simboli devono essere lì anche quando ritorna la funzione circostante. Il problema è che questi simboli sono associati ad una memorizzazione locale della funzione (di solito sullo stack delle chiamate), che non sarà più presente quando la funzione ritorna. Pertanto, affinché un lambda funzioni come ti aspetti, devi in ​​qualche modo "catturare" tutte queste variabili libere dal suo contesto esterno e salvarle per dopo, anche quando il contesto esterno sarà scomparso. Cioè, devi trovare la chiusura del tuo lambda (tutte queste variabili esterne che usa) e memorizzarlo da qualche altra parte (sia facendo una copia, sia preparando lo spazio in anticipo, da qualche altra parte che in pila). Il metodo effettivo che utilizzi per raggiungere questo obiettivo è un "dettaglio di implementazione" della tua lingua. Ciò che è importante qui è la chiusura , che è l'insieme di variabili libere dall'ambiente del tuo lambda che devono essere salvate da qualche parte.

Non ci è voluto troppo tempo perché le persone iniziassero a chiamare la struttura dei dati effettiva che usano nelle implementazioni del loro linguaggio per implementare la chiusura come "chiusura" stessa. La struttura di solito assomiglia a qualcosa del genere:

Closure {
   [pointer to the lambda function's machine code],
   [pointer to the lambda function's environment]
}

e queste strutture dati vengono passate come parametri ad altre funzioni, restituite da funzioni e archiviate in variabili, per rappresentare lambda e consentire loro di accedere al loro ambiente di chiusura e al codice macchina da eseguire in quel contesto. Ma è solo un modo (uno dei tanti) per implementare la chiusura, non la chiusura stessa.

Come ho spiegato sopra, la chiusura di un'espressione lambda è il sottoinsieme di definizioni nel suo ambiente che danno valori alle variabili libere contenute in quell'espressione lambda, chiudendo efficacemente l'espressione (trasformando un'espressione lambda aperta , che non può essere ancora valutata, in un'espressione lambda chiusa , che può essere valutata, dal momento che tutti i simboli in esso contenuti sono ora definiti).

Qualsiasi altra cosa è solo un "culto del carico" e una "magia voo-doo" di programmatori e venditori di linguaggi inconsapevoli delle vere radici di queste nozioni.

Spero che risponda alle tue domande. Ma se hai delle domande di follow-up, sentiti libero di chiedere loro nei commenti, e cercherò di spiegarlo meglio.


Dipende se una funzione utilizza una variabile esterna o meno per eseguire un'operazione.

Variabili esterne : variabili definite al di fuori dell'ambito di una funzione.

  • Le espressioni Lambda sono senza stato perché dipende da parametri, variabili interne o costanti per eseguire operazioni.

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • Le chiusure mantengono lo stato perché utilizza variabili esterne (cioè variabili definite al di fuori dell'ambito del corpo della funzione) insieme a parametri e costanti per eseguire operazioni.

    int n = 2
    
    Function<Integer,Integer> closure = t -> {
        return t * n 
    }
    

Quando Java crea una chiusura, mantiene la variabile n con la funzione in modo che possa essere referenziata quando viene passata ad altre funzioni o utilizzata ovunque.


Il concetto è lo stesso descritto sopra, ma se sei di origine PHP, spiega ulteriormente l'uso del codice PHP.

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

function ($ v) {return $ v> 2; } è la definizione della funzione lambda. Possiamo persino memorizzarlo in una variabile, quindi può essere riusabile:

$max = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);

Ora, cosa succede se si desidera modificare il numero massimo consentito nell'array filtrato? Dovresti scrivere un'altra funzione lambda o creare una chiusura (PHP 5.3):

$max_comp = function ($max) {
  return function ($v) use ($max) { return $v > $max; };
};

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));

Una chiusura è una funzione valutata nel proprio ambiente, che ha una o più variabili associate a cui è possibile accedere quando viene chiamata la funzione. Provengono dal mondo della programmazione funzionale, dove ci sono una serie di concetti in gioco. Le chiusure sono come funzioni lambda, ma più intelligenti nel senso che hanno la capacità di interagire con le variabili dall'ambiente esterno di dove è definita la chiusura.

Ecco un esempio più semplice di chiusura di PHP:

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

Ben spiegato in questo articolo.


Non tutte le chiusure sono lambda e non tutti i lambda sono chiusure. Entrambe sono funzioni, ma non necessariamente nel modo in cui siamo abituati a sapere.

Un lambda è essenzialmente una funzione definita in linea piuttosto che il metodo standard di dichiarazione delle funzioni. Lambdas può essere spesso passato in giro come oggetti.

Una chiusura è una funzione che racchiude lo stato circostante facendo riferimento a campi esterni al suo corpo. Lo stato chiuso rimane attraverso le invocazioni della chiusura.

In un linguaggio orientato agli oggetti, le chiusure sono normalmente fornite attraverso gli oggetti. Tuttavia, alcuni linguaggi OO (ad es. C #) implementano funzionalità speciali più vicine alla definizione di chiusure fornite da linguaggi puramente funzionali (come il lisp) che non hanno oggetti da includere nello stato.

La cosa interessante è che l'introduzione di Lambdas e Closures in C # avvicina la programmazione funzionale all'utilizzo tradizionale.


Quando la maggior parte delle persone pensa alle funzioni , pensa alle funzioni con nome :

function foo() { return "This string is returned from the 'foo' function"; }

Questi sono chiamati per nome, ovviamente:

foo(); //returns the string above

Con espressioni lambda , puoi avere funzioni anonime :

 @foo = lambda() {return "This is returned from a function without a name";}

Con l'esempio sopra, puoi chiamare il lambda attraverso la variabile a cui è stato assegnato:

foo();

Più utile dell'assegnazione di funzioni anonime alle variabili, tuttavia, le passano a o da funzioni di ordine superiore, ovvero funzioni che accettano / restituiscono altre funzioni. In molti di questi casi, non è necessario nominare una funzione:

function filter(list, predicate) 
 { @filteredList = [];
   for-each (@x in list) if (predicate(x)) filteredList.add(x);
   return filteredList;
 }

//filter for even numbers
filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)}); 

Una chiusura può essere una funzione denominata o anonima, ma è conosciuta come tale quando "chiude sopra" le variabili nello scope in cui è definita la funzione, cioè, la chiusura farà ancora riferimento all'ambiente con qualsiasi variabile esterna che viene utilizzata nel chiusura stessa. Ecco una chiusura con nome:

@x = 0;

function incrementX() { x = x + 1;}

incrementX(); // x now equals 1

Non sembra molto, ma cosa succederebbe se fosse tutto in un'altra funzione e hai passato incrementX a una funzione esterna?

function foo()
 { @x = 0;

   function incrementX() 
    { x = x + 1;
      return x;
    }

   return incrementX;
 }

@y = foo(); // y = closure of incrementX over foo.x
y(); //returns 1 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)

Questo è il modo in cui ottieni oggetti di stato nella programmazione funzionale. Poiché la denominazione "incrementX" non è necessaria, è possibile utilizzare un lambda in questo caso:

function foo()
 { @x = 0;

   return lambda() 
           { x = x + 1;
             return x;
           };
 }

Un lambda è solo una funzione anonima, una funzione definita senza nome. In alcune lingue, come Scheme, sono equivalenti alle funzioni con nome. In effetti, la definizione della funzione viene riscritta come vincolante di un lambda a una variabile internamente. In altre lingue, come Python, ci sono alcune (piuttosto inutili) distinzioni tra di loro, ma si comportano allo stesso modo in caso contrario.

Una chiusura è una funzione che si chiude sull'ambiente in cui è stata definita. Ciò significa che può accedere a variabili non incluse nell'elenco dei parametri. Esempi:

def func(): return h
def anotherfunc(h):
   return func()

Ciò causerà un errore, perché func non si chiude sull'ambiente in un altro punto - h non è definito. func si chiude solo sull'ambiente globale. Questo funzionerà:

def anotherfunc(h):
    def func(): return h
    return func()

Perché qui, func è definita in anotherfunc , e in python 2.3 e superiore (o qualche numero come questo) quando hanno quasi le chiusure corrette (la mutazione continua a non funzionare), questo significa che si chiude sull'ambiente di anotherfunc e può accedere variabili al suo interno. In Python 3.1+, la mutazione funziona anche quando si utilizza la parola chiave nonlocal .

Un altro punto importante - func continuerà a chiudersi sull'ambiente di anotherfunc anche quando non viene più valutato in anotherfunc . Questo codice funzionerà anche:

def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

Questo stamperà 10.

Questo, come si nota, non ha nulla a che vedere con i lambda : sono due concetti diversi (sebbene correlati).


Un lambda è solo una funzione anonima, una funzione definita senza nome. Una chiusura è una funzione che si chiude sull'ambiente in cui è stata definita. Ciò significa che può accedere a variabili non incluse nell'elenco dei parametri.





closures