Domanda

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

E ora che siamo qui, in cosa differiscono da una normale funzione?

È stato utile?

Soluzione

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 associazione interna di una lambda a una variabile. In altre lingue, come Python, ci sono alcune (piuttosto inutili) distinzioni tra loro, ma si comportano allo stesso modo altrimenti.

Una chiusura è qualsiasi funzione che chiude l'ambiente ambiente in cui è stata definita. Ciò significa che può accedere a variabili non presenti nell'elenco dei parametri. Esempi:

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

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

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

Perché qui func è definito in anotherfunc , e in python 2.3 e versioni successive (o in alcuni numeri come questo) quando quasi hanno chiusure corrette (la mutazione continua a non funzionare), ciò significa che si chiude sull'ambiente anotherfunc e può accedere alle variabili al suo interno. In Python 3.1+, anche la mutazione funziona quando si utilizza il nonlocal parola chiave .

Un altro punto importante: func continuerà a chiudere l'ambiente 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)()

Verrà stampato 10.

Questo, come notate, non ha nulla a che fare con lambda s - sono due concetti diversi (sebbene correlati).

Altri suggerimenti

C'è molta confusione tra lambda e chiusure, anche nelle risposte a questa domanda StackOverflow qui. Invece di chiedere ai programmatori casuali che hanno appreso delle chiusure dalla pratica con determinati linguaggi di programmazione o altri programmatori indecisi, fai un viaggio verso la fonte (dove tutto è iniziato). E poiché lambda e chiusure provengono da Calcolo lambda 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 al suo interno: & # 9658;

  • APPLICAZIONE: Applicazione di un'espressione a un'altra, indicata con fx .
    (Considerala come una chiamata di funzione , dove f è la funzione e x è il suo unico parametro)
  • ASTRAZIONE: lega un simbolo che si verifica in un'espressione per indicare che questo simbolo è solo uno "spazio", una casella vuota in attesa di essere riempita con valore, una "variabile" com'era. Viene fatto anteponendo una lettera greca & # 955; (lambda), quindi il nome simbolico (ad es. x ), quindi un punto . prima dell'espressione. Questo quindi converte l'espressione in una funzione aspettandosi un parametro .
    Ad esempio: & # 955; x.x + 2 accetta il espressione x + 2 e indica che il simbolo x in questa espressione è una variabile associata & # 8211; può essere sostituito con un valore fornito come parametro.
    Nota che la funzione definita in questo modo è anonimo & # 8211; non ha un nome, quindi non puoi ancora fare riferimento ad esso, ma puoi chiamarlo immediatamente (ricordi l'applicazione?) fornendogli il parametro che sta aspettando, in questo modo: < codice> (& # 955; 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 tu ottieni 7 + 2 , che quindi si riduce a 9 secondo le comuni regole aritmetiche.

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


In diversi linguaggi di programmazione, la sintassi per l'astrazione funzionale (lambda) può differire. Ad esempio, in JavaScript è simile al seguente:

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 qualche variabile:

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

che in realtà gli dà un nome f , che ti consente di fare riferimento ad esso e chiamarlo più volte in seguito, ad esempio:

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

Ma non dovevi nominarlo. Puoi chiamarlo immediatamente:

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

In LISP, i lambda sono fatti in questo modo:

(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 fa l'astrazione lambda è vincolante un simbolo nella sua sottoespressione, in modo che diventi un parametro sostituibile . Tale simbolo si chiama associato . E se ci fossero altri simboli nell'espressione? Ad esempio: & # 955; x.x / y + 2 . In questa espressione, il simbolo x è associato all'astrazione lambda & # 955; x. che lo precede. Ma l'altro simbolo, y , non è associato & # 8211; è gratuito . Non sappiamo cosa sia e da dove venga, quindi non sappiamo cosa significhi e quale valore rappresenti, e quindi non possiamo valutare quell'espressione finché non scopriamo cosa significa y .

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

Puoi pensare ai simboli gratuiti 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 cui questa espressione fa parte (come diceva Qui-Gon Jinn: " C'è sempre un pesce più grande ";)), o in qualche biblioteca, o nel linguaggio stesso (come primitivo ).

Questo ci consente di dividere le espressioni lambda in due categorie:

  • Espressioni CHIUSE: ogni simbolo che si presenta in queste espressioni è associato da un'astrazione lambda. In altre parole, sono autonomi ; non richiedono alcun contesto circostante per essere valutati. Sono anche chiamati combinatori .
  • espressioni APERTE: alcuni simboli in queste espressioni non sono associati & # 8211; vale a dire, alcuni dei simboli presenti in essi sono gratuiti e richiedono alcune informazioni esterne, e quindi non possono essere valutati fino a quando non fornisci le definizioni di questi simboli.

Puoi CHIUDI un'espressione apri lambda fornendo ambiente , che definisce tutti questi simboli liberi legandoli ad alcuni valori (che possono essere numeri, stringhe, anonimi funzioni alias lambda, qualunque cosa & # 8230;).

Ed ecco la parte chiusura :
La chiusura di una espressione lambda è questa particolare serie di simboli definiti nel contesto esterno (ambiente) che danno valori ai simboli liberi in questo espressione, rendendoli più non liberi. Trasforma un'espressione lambda aperta , che contiene ancora alcune "indefinite" simboli gratuiti, in uno chiuso , che non ha più simboli liberi.

Ad esempio, se hai la seguente espressione lambda: & # 955; xx / y + 2 , il simbolo x è associato, mentre il simbolo y è gratuito, quindi l'espressione è open e non può essere valutata a meno che non si dica cosa significa y (e lo stesso con + e 2 , anch'essi gratuiti). Ma supponi di avere anche un ambiente come questo:

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

Questo ambiente fornisce definizioni per tutti i "non definiti" (gratuiti) simboli 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. È da qui che deriva il nome chiusura , ed è per questo che le risposte di così tante 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 di dati in memoria, o alcune caratteristiche dei linguaggi che usano, o perché confondono le chiusure con lambdas? : P

Bene, i responsabili aziendali di Sun / Oracle, Microsoft, Google ecc. sono da biasimare, perché è quello che hanno chiamato questi costrutti nelle loro lingue (Java, C #, Go ecc.). Spesso chiamano " chiusure " quelli che dovrebbero essere solo lambda. Oppure chiamano "chiusure" una particolare tecnica utilizzata per implementare l'ambito lessicale, ovvero il fatto che una funzione può accedere alle variabili che sono state definite nel suo ambito esterno al momento della sua definizione. Spesso dicono che la funzione "racchiude" queste variabili, cioè, le acquisiscono in una struttura di dati per salvarle dalla distruzione al termine dell'esecuzione della funzione esterna. Ma questo è solo post factum inventato " etimologia folcloristica " e marketing, il che rende le cose più confuse, perché ogni fornitore di lingue usa la propria terminologia.

Ed è anche peggio a causa del fatto che c'è sempre un po 'di verità in quello che dicono, che non ti permette di liquidarlo facilmente come falso: P Lasciami spiegare:

Se vuoi implementare un linguaggio che usa lambda come cittadini di prima classe, devi consentire loro di usare simboli definiti nel loro contesto circostante (cioè, di usare variabili libere nei tuoi lambda). E questi simboli devono essere presenti anche quando ritorna la funzione circostante. Il problema è che questi simboli sono associati a una memoria locale della funzione (di solito nello stack di chiamate), che non sarà più presente quando la funzione ritorna. Pertanto, affinché una lambda funzioni nel modo previsto, è necessario 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 della tua lambda (tutte queste variabili esterne che usa) e archiviarla da qualche altra parte (o facendo una copia o preparando spazio per loro 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 molto tempo prima che le persone iniziassero a chiamare la struttura dati effettiva che usano nelle implementazioni del loro linguaggio per implementare la chiusura come "chiusura". si. La struttura di solito è simile a questa:

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

e queste strutture di dati vengono trasferite come parametri ad altre funzioni, restituite da funzioni e archiviate in variabili, per rappresentare lambdas 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 stessa chiusura.

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, efficacemente chiudendo l'espressione (trasformando un < em> open espressione lambda, che non può ancora essere valutata, in un'espressione chiusa lambda, che può quindi essere valutata, dal momento che tutti i simboli in essa contenuti sono ora definiti).

Tutto il resto è solo un "culto del carico" e "voo-doo magic" di programmatori e venditori di lingue ignari delle vere radici di queste nozioni.

Spero che risponda alle tue domande. Ma se hai domande di follow-up, sentiti libero di farle nei commenti e proverò a spiegarlo meglio.

Quando la maggior parte delle persone pensa a funzioni , pensa a funzioni nominate :

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 la lambda attraverso la variabile a cui è stata assegnata:

foo();

Più utile dell'assegnazione di funzioni anonime alle variabili, tuttavia, le stanno passando ao da funzioni di ordine superiore, ovvero funzioni che accettano / restituiscono altre funzioni. In molti di questi casi, la denominazione di una funzione non è necessaria:

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 è nota come tale quando si chiude su " variabili nell'ambito in cui è definita la funzione, ovvero la chiusura farà ancora riferimento all'ambiente con eventuali variabili esterne utilizzate nella chiusura stessa. Ecco una chiusura denominata:

@x = 0;

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

incrementX(); // x now equals 1

Non sembra molto, ma cosa succederebbe se tutto fosse in un'altra funzione e passassi 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)

Ecco come ottenere oggetti con stato nella programmazione funzionale. Dalla denominazione "incrementX" non è necessario, puoi usare un lambda in questo caso:

function foo()
 { @x = 0;

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

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

Una lambda è essenzialmente una funzione definita in linea piuttosto che il metodo standard di dichiarazione delle funzioni. Le lambda possono spesso essere passate in giro come oggetti.

Una chiusura è una funzione che racchiude il suo stato circostante facendo riferimento a campi esterni al suo corpo. Lo stato racchiuso rimane invocazioni della chiusura.

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

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

È semplice come questo: lambda è un costrutto di linguaggio, cioè semplicemente sintassi per funzioni anonime; una chiusura è una tecnica per implementarla - o qualsiasi funzione di prima classe, per quella materia, denominata o anonima.

Più precisamente, una chiusura è come una funzione di prima classe in fase di esecuzione , come 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 sono già usciti gli ambiti esterni da cui hanno origine.

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

Dal punto di vista dei linguaggi di programmazione, sono completamente due cose diverse.

Fondamentalmente per un linguaggio completo di Turing abbiamo solo bisogno di elementi molto limitati, ad es. astrazione, applicazione e riduzione. L'astrazione e l'applicazione forniscono il modo in cui puoi costruire l'espressione lamdba e la riduzione altera il significato dell'espressione lambda.

Lambda fornisce un modo per estrarre il processo di calcolo. per esempio, per calcolare la somma di due numeri, un processo che accetta due parametri x, y e restituisce x + y può essere estratto. Nello schema, puoi scriverlo come

(lambda (x y) (+ x y))

È possibile rinominare i parametri, ma l'attività che completa non cambia. In quasi tutti i linguaggi di programmazione, puoi dare un nome all'espressione lambda, che sono denominate funzioni. Ma non c'è molta differenza, possono essere concettualmente considerati solo come zucchero di sintassi.

OK, ora immagina come può essere implementato. Ogni volta che applichiamo l'espressione lambda ad alcune espressioni, ad esempio

((lambda (x y) (+ x y)) 2 3)

Possiamo semplicemente sostituire i parametri con l'espressione da valutare. Questo modello è già molto potente. Ma questo modello non ci consente di modificare i valori dei simboli, ad es. Non possiamo imitare il cambiamento di stato. Quindi abbiamo bisogno di un modello più complesso. Per farla breve, ogni volta che vogliamo calcolare il significato dell'espressione lambda, inseriamo la coppia di simboli e il valore corrispondente in un ambiente (o tabella). Quindi il resto (+ x y) viene valutato cercando i simboli corrispondenti nella tabella. Ora, se forniamo alcune primitive per operare direttamente sull'ambiente, possiamo modellare i cambiamenti di stato!

Con questo sfondo, controlla questa funzione:

(lambda (x y) (+ x y z))

Sappiamo che quando valutiamo l'espressione lambda, x y verrà associato in una nuova tabella. Ma come e dove possiamo cercare z? In realtà z si chiama una variabile libera. Ci deve essere un esterno un ambiente che contiene z. Altrimenti il ??significato dell'espressione non può essere determinato legando solo xey. Per chiarire questo, puoi scrivere qualcosa come segue nello schema:

((lambda (z) (lambda (x y) (+ x y z))) 1)

Quindi z sarebbe associato a 1 in una tabella esterna. Otteniamo ancora una funzione che accetta due parametri, ma il suo significato reale dipende anche dall'ambiente esterno. In altre parole, l'ambiente esterno si chiude sulle variabili libere. Con l'aiuto di set !, possiamo rendere la funzione stateful, cioè non è una funzione nel senso della matematica. Ciò che restituisce non dipende solo dall'input, ma anche da z.

Questo è qualcosa che già conosci molto bene, un metodo di oggetti si basa quasi sempre sullo stato degli oggetti. Ecco perché alcune persone dicono che le chiusure sono oggetti dei poveri. & Quot; Ma potremmo anche considerare gli oggetti come le chiusure dei poveri poiché ci piacciono molto le funzioni di prima classe.

Uso lo schema per illustrare le idee a causa di quello schema è uno dei primi linguaggi che ha delle vere chiusure. Tutti i materiali qui sono presentati molto meglio nel capitolo 3. della SICP

Per riassumere, lambda e chiusura sono concetti davvero diversi. Una lambda è una funzione. Una chiusura è una coppia di lambda e l'ambiente corrispondente che chiude la lambda.

Il concetto è lo stesso descritto sopra, ma se si proviene da background PHP, questo spiega ulteriormente usando il codice PHP.

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

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

$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 che viene 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 le funzioni lambda, ma più intelligenti nel senso che hanno la capacità di interagire con le variabili dall'ambiente esterno in cui è 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.

Questa domanda è vecchia e ha ottenuto molte risposte. Ora con Java 8 e Official Lambda che sono progetti di chiusura non ufficiali, fa rivivere la domanda.

La risposta nel contesto Java (tramite Lambdas e chiusure - qual è il differenza ):?

  

" Una chiusura è un'espressione lambda accoppiata con un ambiente che lega ciascuna delle sue variabili libere a un valore. In Java, le espressioni lambda verranno implementate mediante chiusure, quindi i due termini sono stati usati in modo intercambiabile nella comunità. & Quot;

In parole povere, la chiusura è un trucco sull'ambito, lambda è una funzione anonima. Possiamo realizzare la chiusura con lambda in modo più elegante e lambda viene spesso utilizzato come parametro passato a una funzione superiore

Un'espressione Lambda è solo una funzione anonima. in java semplice, ad esempio, puoi scriverlo in questo modo:

Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
    public Job apply(Person person) {
        Job job = new Job(person.getPersonId(), person.getJobDescription());
        return job;
    }
};

dove la classe Function è appena costruita nel codice java. Ora puoi chiamare mapPersonToJob.apply (persona) da qualche parte per usarlo. questo è solo un esempio. Quello è un lambda prima che ci fosse sintassi per esso. Lambdas una scorciatoia per questo.

di chiusura:

una Lambda diventa una chiusura quando può accedere alle variabili al di fuori di questo ambito. immagino che tu possa dire la sua magia, magicamente può avvolgere l'ambiente in cui è stato creato e usare le variabili al di fuori del suo ambito (ambito esterno. Quindi, per essere chiari, una chiusura significa che un lambda può accedere al suo SCOPO ESTERNO.

in Kotlin, un lambda può sempre accedere alla sua chiusura (le variabili che si trovano nel suo ambito esterno)

Dipende dal fatto che una funzione usi o meno una variabile esterna per eseguire l'operazione.

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

  • Le espressioni lambda sono apolidi perché dipendono da parametri, variabili interne o costanti per eseguire operazioni.

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • Chiude hold state perché utilizza variabili esterne (ovvero 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 la chiusura, mantiene la variabile n con la funzione in modo che possa essere referenziata quando passata ad altre funzioni o utilizzata ovunque.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top