Was ist der Unterschied zwischen einem "Verschluss" und einem "Lambda"?


811

Könnte jemand erklären? Ich verstehe die grundlegenden Konzepte dahinter, aber ich sehe sie oft synonym verwendet und bin verwirrt.

Und jetzt, wo wir hier sind, wie unterscheiden sie sich von einer regulären Funktion?


85
Lambdas sind ein Sprachkonstrukt (anonyme Funktionen), Closures sind eine Implementierungstechnik zur Implementierung erstklassiger Funktionen (ob anonym oder nicht). Leider wird dies oft von vielen Menschen verwechselt.
Andreas Rossberg



Informationen zu PHP-Schließungen finden Sie unter php.net/manual/en/class.closure.php . Es ist nicht das, was ein JavaScript-Programmierer erwarten würde.
PaulH

Die Antwort von SasQ ist ausgezeichnet. IMHO wäre diese Frage für SO-Benutzer nützlicher, wenn sie die Zuschauer zu dieser Antwort führen würde.
AmigoNico

Antworten:


703

Ein Lambda ist nur eine anonyme Funktion - eine Funktion ohne Namen. In einigen Sprachen, wie z. B. Schema, entsprechen sie benannten Funktionen. Tatsächlich wird die Funktionsdefinition so umgeschrieben, dass ein Lambda intern an eine Variable gebunden wird. In anderen Sprachen wie Python gibt es einige (ziemlich unnötige) Unterschiede zwischen ihnen, aber sie verhalten sich ansonsten genauso.

Ein Abschluss ist eine Funktion, die über die Umgebung geschlossen wird, in der sie definiert wurde. Dies bedeutet, dass auf Variablen zugegriffen werden kann, die nicht in der Parameterliste enthalten sind. Beispiele:

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

Dies führt zu einem Fehler, da die Umgebung funcnicht geschlossen wirdanotherfunc - hist undefiniert. funcschließt nur über die globale Umgebung. Das wird funktionieren:

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

Denn hier funcist definiert in anotherfuncund in Python 2.3 und höher (oder eine Zahl wie diese) , wenn sie fast bekamen Schließungen korrigieren (Mutation noch nicht funktioniert), bedeutet dies , dass es über schließt anotherfunc ‚s - Umgebung und kann den Zugriff Variablen innerhalb von es. In Python 3.1+ funktioniert die Mutation auch, wenn das nonlocalSchlüsselwort verwendet wird .

Ein weiterer wichtiger Punkt - funcwird die anotherfuncUmgebung weiterhin schließen , auch wenn sie nicht mehr ausgewertet wird anotherfunc. Dieser Code funktioniert auch:

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

print anotherfunc(10)()

Dies wird 10 drucken.

Wie Sie bemerken, hat dies nichts mit Lambda zu tun - es handelt sich um zwei verschiedene (obwohl verwandte) Konzepte.


1
Claudiu, meines Wissens hat Python nie ganz die richtigen Abschlüsse bekommen. Haben sie das Veränderbarkeitsproblem behoben, während ich nicht gesucht habe? Ganz möglich ...
Simon

simon - du hast recht, sie sind immer noch nicht ganz korrekt. Ich denke, Python 3.0 wird einen Hack hinzufügen, um dies zu umgehen. (so etwas wie eine 'nichtlokale' Kennung). Ich werde meine Antwort ändern, um das zu reflektieren.
Claudiu

2
@ AlexanderOrlov: Sie sind beide Lambdas und Verschlüsse. Java hatte zuvor Schließungen über anonyme innere Klassen. Jetzt wurde diese Funktionalität über Lambda-Ausdrücke syntaktisch vereinfacht. Der wahrscheinlich relevanteste Aspekt der neuen Funktion ist also, dass es jetzt Lambdas gibt. Es ist nicht falsch, sie Lambdas zu nennen, sie sind in der Tat Lambdas. Warum Java 8-Autoren sich dafür entscheiden, die Tatsache, dass es sich um Verschlüsse handelt, nicht hervorzuheben, weiß ich nicht.
Claudiu

2
@AlexanderOrlov Da Java 8-Lambdas keine echten Abschlüsse sind, handelt es sich um Simulationen von Abschlüssen. Sie ähneln eher Python 2.3-Closures (keine Veränderlichkeit, daher müssen die Variablen, auf die verwiesen wird, "effektiv endgültig" sein) und werden intern zu Nicht-Closure-Funktionen kompiliert, bei denen alle Variablen, auf die im umschließenden Bereich verwiesen wird, als versteckte Parameter verwendet werden.
Logan Pickup

7
@Claudiu Ich denke, der Verweis auf eine bestimmte Sprachimplementierung (Python) kann die Antwort überkomplizieren. Die Frage ist vollständig sprachunabhängig (und hat keine sprachspezifischen Tags).
Matthew

318

Es gibt viel Verwirrung um Lambdas und Verschlüsse, selbst in den Antworten auf diese StackOverflow-Frage hier. Anstatt zufällige Programmierer zu fragen, die durch das Üben mit bestimmten Programmiersprachen oder anderen ahnungslosen Programmierern etwas über das Schließen gelernt haben, machen Sie eine Reise zur Quelle (wo alles begann). Und da Lambdas und Verschlüsse von Lambda Calculus stammen, der von Alonzo Church in den 30er Jahren erfunden wurde, bevor es überhaupt erste elektronische Computer gab, ist dies die Quelle, über die ich spreche.

Lambda Calculus ist die einfachste Programmiersprache der Welt. Die einzigen Dinge, die Sie darin tun können: ►

  • ANWENDUNG: Anwenden eines Ausdrucks auf einen anderen, bezeichnet f x.
    (Betrachten Sie es als einen Funktionsaufruf , bei dem fsich die Funktion befindet und xder einzige Parameter ist.)
  • ZUSAMMENFASSUNG: Bindet ein in einem Ausdruck vorkommendes Symbol, um zu kennzeichnen, dass dieses Symbol nur ein "Slot" ist, ein leeres Feld, das darauf wartet, mit Wert gefüllt zu werden, sozusagen eine "Variable". Dazu wird ein griechischer Buchstabe λ(Lambda) vorangestellt , dann der symbolische Name (z. B. x) und dann ein Punkt .vor dem Ausdruck. Dies konvertiert dann den Ausdruck in eine Funktion, die einen Parameter erwartet .
    Beispiel: λx.x+2Nimmt den Ausdruck x+2und teilt mit, dass das Symbol xin diesem Ausdruck eine gebundene Variable ist. Es kann durch einen Wert ersetzt werden, den Sie als Parameter angeben.
    Beachten Sie, dass die auf diese Weise definierte Funktion anonym ist- Es hat keinen Namen, daher können Sie noch nicht darauf verweisen, aber Sie können es sofort aufrufen (sich an die Anwendung erinnern?), Indem Sie den Parameter angeben, auf den es wartet, wie folgt : (λx.x+2) 7. Dann wird der Ausdruck (in diesem Fall ein Literalwert) 7wie xim Unterausdruck x+2des angewendeten Lambda ersetzt, so dass Sie erhalten 7+2, der sich dann 9durch gemeinsame Arithmetikregeln reduziert .

Wir haben also eines der Rätsel gelöst:
Lambda ist die anonyme Funktion aus dem obigen Beispiel λx.x+2.


In verschiedenen Programmiersprachen kann die Syntax für die funktionale Abstraktion (Lambda) unterschiedlich sein. In JavaScript sieht es beispielsweise so aus:

function(x) { return x+2; }

und Sie können es sofort auf einen Parameter wie diesen anwenden:

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

oder Sie können diese anonyme Funktion (Lambda) in einer Variablen speichern:

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

fDadurch erhält es effektiv einen Namen , sodass Sie darauf verweisen und es später mehrmals aufrufen können, z.

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

Aber du musstest es nicht benennen. Man könnte es sofort nennen:

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

In LISP werden Lambdas wie folgt hergestellt:

(lambda (x) (+ x 2))

und Sie können ein solches Lambda aufrufen, indem Sie es sofort auf einen Parameter anwenden:

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


OK, jetzt ist es Zeit , das andere Rätsel zu lösen: Was ist ein Verschluss . Lassen Sie uns dazu über Symbole ( Variablen ) in Lambda-Ausdrücken sprechen .

Wie gesagt, die Lambda-Abstraktion bindet ein Symbol in seinem Unterausdruck, so dass es zu einem ersetzbaren Parameter wird . Ein solches Symbol heißt gebunden . Aber was ist, wenn der Ausdruck andere Symbole enthält? Zum Beispiel : λx.x/y+2. In diesem Ausdruck ist das Symbol xan die λx.vorangegangene Lambda-Abstraktion gebunden . Aber das andere Symbol yist nicht gebunden - es ist frei . Wir wissen nicht, was es ist und woher es kommt, also wissen wir nicht, was es bedeutet und welchen Wert es darstellt, und deshalb können wir diesen Ausdruck nicht bewerten, bis wir herausgefunden haben, was ybedeutet.

Tatsächlich gilt das Gleiche für die beiden anderen Symbole 2und +. Es ist nur so, dass wir mit diesen beiden Symbolen so vertraut sind, dass wir normalerweise vergessen, dass der Computer sie nicht kennt, und wir müssen ihm sagen, was sie bedeuten, indem wir sie irgendwo definieren, z. B. in einer Bibliothek oder in der Sprache selbst.

Sie können sich die freien Symbole vorstellen, die an einer anderen Stelle außerhalb des Ausdrucks in ihrem "umgebenden Kontext" definiert sind, der als Umgebung bezeichnet wird . Die Umgebung könnte ein größerer Ausdruck sein, zu dem dieser Ausdruck gehört (wie Qui-Gon Jinn sagte: "Es gibt immer einen größeren Fisch";)), oder in einer Bibliothek oder in der Sprache selbst (als Primitiv ).

Auf diese Weise können wir Lambda-Ausdrücke in zwei Kategorien einteilen:

  • GESCHLOSSENE Ausdrücke: Jedes Symbol, das in diesen Ausdrücken vorkommt, ist an eine Lambda-Abstraktion gebunden . Mit anderen Worten, sie sind in sich geschlossen ; Sie erfordern keine Bewertung des umgebenden Kontexts. Sie werden auch Kombinatoren genannt .
  • OPEN-Ausdrücke: Einige Symbole in diesen Ausdrücken sind nicht gebunden. Das heißt, einige der in ihnen vorkommenden Symbole sind frei und erfordern externe Informationen. Sie können daher erst ausgewertet werden, wenn Sie die Definitionen dieser Symbole angeben.

Sie können einen offenen Lambda-Ausdruck schließen, indem Sie die Umgebung angeben , die alle diese freien Symbole definiert, indem Sie sie an einige Werte binden (z. B. Zahlen, Zeichenfolgen, anonyme Funktionen, auch bekannt als Lambdas usw.).

Und hier kommt der Abschluss- Teil:
Der Abschluss eines Lambda-Ausdrucks ist diese bestimmte Menge von Symbolen, die im äußeren Kontext (Umgebung) definiert sind und den freien Symbolen in diesem Ausdruck Werte geben , wodurch sie nicht mehr frei sind. Es verwandelt einen offenen Lambda-Ausdruck, der noch einige "undefinierte" freie Symbole enthält, in einen geschlossenen , der keine freien Symbole mehr enthält.

Wenn Sie beispielsweise den folgenden Lambda-Ausdruck haben : λx.x/y+2, ist das Symbol xgebunden, während das Symbol yfrei ist. Daher ist openund kann der Ausdruck nicht ausgewertet werden, es sei denn, Sie sagen, was ybedeutet (und dasselbe mit +und 2, die ebenfalls frei sind). Angenommen, Sie haben auch eine Umgebung wie diese:

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

Diese Umgebung liefert Definitionen für alle die „undefiniert“ (kostenlos) Symbole aus unserem Lambda - Ausdruck ( y, +, 2) und mehr zusätzlichen Symbole ( q, w). Die Symbole, die definiert werden müssen, sind diese Teilmenge der Umgebung:

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

und genau das ist der Abschluss unseres Lambda-Ausdrucks:>

Mit anderen Worten, es schließt einen offenen Lambda-Ausdruck. Dies ist , wo der Name Schließung von in erster Linie kam, und das ist , warum so viele Menschen der Antworten in diesem Thread nicht ganz richtig: P


Warum irren sie sich? Warum sagen so viele von ihnen, dass Schließungen einige Datenstrukturen im Speicher oder einige Merkmale der von ihnen verwendeten Sprachen sind, oder warum verwechseln sie Schließungen mit Lambdas? : P.

Nun, die Corporate Marketoids von Sun / Oracle, Microsoft, Google usw. sind schuld, denn so haben sie diese Konstrukte in ihren Sprachen (Java, C #, Go usw.) genannt. Sie nennen oft "Verschlüsse", was nur Lambdas sein sollen. Oder sie nennen "Verschlüsse" eine bestimmte Technik, mit der sie das lexikalische Scoping implementiert haben, dh die Tatsache, dass eine Funktion auf die Variablen zugreifen kann, die zum Zeitpunkt ihrer Definition in ihrem äußeren Bereich definiert waren. Sie sagen oft, dass die Funktion diese Variablen "einschließt", dh sie in einer Datenstruktur erfasst, um zu verhindern, dass sie nach Abschluss der Ausführung der äußeren Funktion zerstört werden. Aber dies ist nur eine post-factum erfundene "Folklore-Etymologie" und Marketing, was die Dinge nur verwirrender macht.

Und es ist noch schlimmer, weil das, was sie sagen, immer ein bisschen Wahrheit enthält, was es Ihnen nicht erlaubt, es leicht als falsch abzutun: P Lassen Sie mich erklären:

Wenn Sie eine Sprache implementieren möchten, die Lambdas als erstklassige Bürger verwendet, müssen Sie ihnen erlauben, Symbole zu verwenden, die in ihrem umgebenden Kontext definiert sind (dh freie Variablen in Ihren Lambdas zu verwenden). Und diese Symbole müssen auch dann vorhanden sein, wenn die umgebende Funktion zurückkehrt. Das Problem ist, dass diese Symbole an einen lokalen Speicher der Funktion gebunden sind (normalerweise auf dem Aufrufstapel), der bei der Rückkehr der Funktion nicht mehr vorhanden ist. Damit ein Lambda so funktioniert, wie Sie es erwarten, müssen Sie daher alle diese freien Variablen aus seinem äußeren Kontext "erfassen" und für später speichern, selbst wenn der äußere Kontext nicht mehr vorhanden ist. Das heißt, Sie müssen den Verschluss findenvon Ihrem Lambda (all diese externen Variablen, die es verwendet) und speichern Sie es an einem anderen Ort (entweder indem Sie eine Kopie erstellen oder indem Sie im Voraus Speicherplatz für sie vorbereiten, an einem anderen Ort als auf dem Stapel). Die eigentliche Methode, mit der Sie dieses Ziel erreichen, ist ein "Implementierungsdetail" Ihrer Sprache. Was hier wichtig ist, ist der Abschluss , bei dem es sich um die Menge der freien Variablen aus der Umgebung Ihres Lambda handelt, die irgendwo gespeichert werden müssen.

Es dauerte nicht lange, bis die Benutzer anfingen, die tatsächliche Datenstruktur, die sie in den Implementierungen ihrer Sprache verwenden, um den Abschluss als "Abschluss" selbst zu implementieren. Die Struktur sieht normalerweise ungefähr so ​​aus:

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

Diese Datenstrukturen werden als Parameter an andere Funktionen weitergegeben, von Funktionen zurückgegeben und in Variablen gespeichert, um Lambdas darzustellen und ihnen den Zugriff auf ihre umschließende Umgebung sowie den in diesem Kontext ausgeführten Maschinencode zu ermöglichen. Aber es ist nur eine Möglichkeit (eine von vielen), die Schließung zu implementieren , nicht die Schließung selbst.

Wie ich oben erklärt habe, ist das Schließen eines Lambda-Ausdrucks die Teilmenge der Definitionen in seiner Umgebung, die den in diesem Lambda-Ausdruck enthaltenen freien Variablen Werte geben und den Ausdruck effektiv schließen (indem ein offener Lambda-Ausdruck, der noch nicht ausgewertet werden kann, in umgewandelt wird) ein geschlossener Lambda-Ausdruck, der dann ausgewertet werden kann, da jetzt alle darin enthaltenen Symbole definiert sind).

Alles andere ist nur ein "Frachtkult" und eine "Voo-Doo-Magie" von Programmierern und Sprachanbietern, die sich der wahren Wurzeln dieser Begriffe nicht bewusst sind.

Ich hoffe das beantwortet deine Fragen. Wenn Sie jedoch weitere Fragen hatten, können Sie diese gerne in den Kommentaren stellen. Ich werde versuchen, dies besser zu erklären.


50
Beste Antwort, die Dinge eher allgemein als sprachspezifisch erklärt
Shishir Arora

39
Ich liebe diese Art der Herangehensweise, wenn ich Dinge erkläre. Erklären Sie von Anfang an, wie die Dinge funktionieren und wie die aktuellen Missverständnisse entstanden sind. Diese Antwort muss nach oben gehen.
Sharky

1
@SasQ> Das Schließen eines Lambda-Ausdrucks ist die Teilmenge der Definitionen in seiner Umgebung, die den in diesem Lambda-Ausdruck enthaltenen freien Variablen Werte zuweisen Umgebung der Lambda-Funktion "ist die Schließung?
Jeff M

3
Obwohl sich Lambda-Kalkül für mich wie Maschinensprache anfühlt, muss ich zustimmen, dass es eine "gefundene" Sprache im Gegensatz zu einer "gemachten" Sprache ist. Und damit viel weniger willkürlichen Konventionen unterworfen und viel besser geeignet, die zugrunde liegende Struktur der Realität zu erfassen. Wir können Einzelheiten in Linq, JavaScript, F # leichter zugänglich / zugänglicher finden, aber der Lambda-Kalkül gelangt ohne Ablenkung zum Kern der Sache.
StevePoling

1
Ich weiß es zu schätzen, dass Sie Ihren Standpunkt mehrmals wiederholt haben und jedes Mal einen leicht anderen Wortlaut haben. Es hilft, das Konzept zu verstärken. Ich wünschte, mehr Leute hätten das getan.
Johnklawlor

175

Wenn die meisten Menschen an Funktionen denken, denken sie an benannte Funktionen :

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

Diese werden natürlich beim Namen genannt:

foo(); //returns the string above

Mit Lambda-Ausdrücken können Sie anonyme Funktionen haben :

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

Mit dem obigen Beispiel können Sie das Lambda über die Variable aufrufen, der es zugewiesen wurde:

foo();

Nützlicher als das Zuweisen anonymer Funktionen zu Variablen ist es jedoch, sie an oder von Funktionen höherer Ordnung zu übergeben, dh Funktionen, die andere Funktionen akzeptieren / zurückgeben. In vielen dieser Fälle ist die Benennung einer Funktion nicht erforderlich:

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)}); 

Ein Abschluss kann eine benannte oder anonyme Funktion sein, wird jedoch als solche bezeichnet, wenn er Variablen in dem Bereich "schließt", in dem die Funktion definiert ist, dh der Abschluss bezieht sich weiterhin auf die Umgebung mit allen äußeren Variablen, die in der verwendet werden Schließung selbst. Hier ist eine benannte Schließung:

@x = 0;

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

incrementX(); // x now equals 1

Das scheint nicht viel zu sein, aber was wäre, wenn dies alles in einer anderen Funktion wäre und Sie an incrementXeine externe Funktion übergeben würden?

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)

Auf diese Weise erhalten Sie statusbehaftete Objekte in der funktionalen Programmierung. Da die Benennung "incrementX" nicht erforderlich ist, können Sie in diesem Fall ein Lambda verwenden:

function foo()
 { @x = 0;

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

14
Welche Sprache sprichst du hier?
Claudiu

7
Es ist im Grunde Pseudocode. Es enthält etwas Lisp und JavaScript sowie eine von mir entworfene Sprache namens "@" ("at"), benannt nach dem Variablendeklarationsoperator.
Mark Cidade

3
@ MarkCidade, also wo ist diese Sprache @? Gibt es eine Dokumentation und einen Download?
Pacerier

5
Warum nicht Javascript nehmen und eine Einschränkung für die Deklaration von Variablen mit führendem @ -Zeichen hinzufügen? Das würde ein wenig Zeit sparen :)
Nemoden

5
@ Pacerier: Ich begann mit der Implementierung der Sprache: github.com/marxidad/At2015
Mark Cidade

54

Nicht alle Verschlüsse sind Lambdas und nicht alle Lambdas sind Verschlüsse. Beides sind Funktionen, aber nicht unbedingt so, wie wir es gewohnt sind.

Ein Lambda ist im Wesentlichen eine Funktion, die inline definiert wird und nicht die Standardmethode zum Deklarieren von Funktionen. Lambdas können häufig als Objekte herumgereicht werden.

Ein Verschluss ist eine Funktion, die seinen Umgebungszustand einschließt, indem sie auf Felder außerhalb ihres Körpers verweist. Der eingeschlossene Zustand bleibt über Aufrufe der Schließung hinweg erhalten.

In einer objektorientierten Sprache werden Verschlüsse normalerweise durch Objekte bereitgestellt. Einige OO-Sprachen (z. B. C #) implementieren jedoch spezielle Funktionen, die näher an der Definition von Abschlüssen liegen, die von rein funktionalen Sprachen (z. B. lisp) bereitgestellt werden , die keine Objekte zum Einschließen des Status enthalten.

Interessant ist, dass die Einführung von Lambdas und Closures in C # die funktionale Programmierung dem Mainstream näher bringt.


Können wir also sagen, dass Verschlüsse eine Teilmenge von Lambdas und Lambdas eine Teilmenge von Funktionen sind?
Sker

Verschlüsse sind eine Untergruppe von Lambdas ... aber Lambdas sind spezieller als normale Funktionen. Wie gesagt, Lambdas werden inline definiert. Im Wesentlichen gibt es keine Möglichkeit, auf sie zu verweisen, es sei denn, sie werden an eine andere Funktion übergeben oder als Rückgabewert zurückgegeben.
Michael Brown

19
Lambdas und Verschlüsse sind jeweils eine Teilmenge aller Funktionen, aber es gibt nur einen Schnittpunkt zwischen Lambdas und Verschlüssen, wobei der sich nicht überschneidende Teil von Verschlüssen als Funktionen bezeichnet wird, die Verschlüsse sind, und nicht sich überschneidende Lamdas in sich geschlossene Funktionen mit vollständigem gebundene Variablen.
Mark Cidade

Meiner Meinung nach sind Lambdas grundlegendere Konzepte als Funktionen. Es kommt wirklich auf die Programmiersprache an.
Wei Qiu

15

So einfach ist das: Lambda ist ein Sprachkonstrukt, dh einfach eine Syntax für anonyme Funktionen. Ein Abschluss ist eine Technik, um ihn zu implementieren - oder erstklassige Funktionen, benannt oder anonym.

Genauer gesagt ist ein Abschluss die Darstellung einer erstklassigen Funktion zur Laufzeit als Paar ihres "Codes" und einer Umgebung, die über alle in diesem Code verwendeten nicht lokalen Variablen "schließt". Auf diese Weise ist auf diese Variablen auch dann noch zugegriffen, wenn die äußeren Bereiche, aus denen sie stammen, bereits beendet wurden.

Leider gibt es viele Sprachen, die Funktionen als erstklassige Werte nicht oder nur in verkrüppelter Form unterstützen. Daher wird der Begriff "Schließung" häufig verwendet, um "die reale Sache" zu unterscheiden.


12

Aus Sicht der Programmiersprachen sind dies zwei völlig verschiedene Dinge.

Grundsätzlich benötigen wir für eine vollständige Turing-Sprache nur sehr begrenzte Elemente, z. B. Abstraktion, Anwendung und Reduktion. Abstraktion und Anwendung bieten die Möglichkeit, einen Lamdba-Ausdruck aufzubauen, und eine Reduktion derterminiert die Bedeutung des Lambda-Ausdrucks.

Lambda bietet eine Möglichkeit, den Berechnungsprozess zu abstrahieren. Um beispielsweise die Summe zweier Zahlen zu berechnen, kann ein Prozess, der zwei Parameter x, y verwendet und x + y zurückgibt, abstrahiert werden. Im Schema können Sie es als schreiben

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

Sie können die Parameter umbenennen, aber die abgeschlossene Aufgabe ändert sich nicht. In fast allen Programmiersprachen können Sie dem Lambda-Ausdruck einen Namen geben, die als Funktionen bezeichnet werden. Aber es gibt keinen großen Unterschied, sie können konzeptionell nur als Syntaxzucker betrachtet werden.

OK, stellen Sie sich jetzt vor, wie dies implementiert werden kann. Wann immer wir den Lambda-Ausdruck auf einige Ausdrücke anwenden, z

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

Wir können die Parameter einfach durch den auszuwertenden Ausdruck ersetzen. Dieses Modell ist bereits sehr leistungsfähig. Dieses Modell ermöglicht es uns jedoch nicht, die Werte von Symbolen zu ändern, z. B. können wir die Statusänderung nicht nachahmen. Wir brauchen also ein komplexeres Modell. Um es kurz zu machen: Wann immer wir die Bedeutung des Lambda-Ausdrucks berechnen möchten, fügen wir das Symbolpaar und den entsprechenden Wert in eine Umgebung (oder Tabelle) ein. Dann wird der Rest (+ xy) ausgewertet, indem die entsprechenden Symbole in der Tabelle nachgeschlagen werden. Wenn wir nun einige Grundelemente bereitstellen, um direkt in der Umgebung zu arbeiten, können wir die Statusänderungen modellieren!

Überprüfen Sie vor diesem Hintergrund diese Funktion:

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

Wir wissen, dass xy ​​bei der Auswertung des Lambda-Ausdrucks in einer neuen Tabelle gebunden wird. Aber wie und wo können wir nachschlagen? Tatsächlich heißt z eine freie Variable. Es muss eine äußere Umgebung geben, die z enthält. Andernfalls kann die Bedeutung des Ausdrucks nicht nur durch Bindung von x und y bestimmt werden. Um dies zu verdeutlichen, können Sie im Schema Folgendes schreiben:

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

Also würde z in einer äußeren Tabelle an 1 gebunden sein. Wir erhalten immer noch eine Funktion, die zwei Parameter akzeptiert, aber die wahre Bedeutung hängt auch von der äußeren Umgebung ab. Mit anderen Worten, die äußere Umgebung schließt die freien Variablen. Mit Hilfe von set! Können wir die Funktion zustandsbehaftet machen, dh es handelt sich nicht um eine Funktion im Sinne von Mathematik. Was es zurückgibt, hängt nicht nur von der Eingabe ab, sondern auch von z.

Dies ist etwas, das Sie bereits sehr gut kennen. Eine Methode von Objekten hängt fast immer vom Zustand der Objekte ab. Deshalb sagen einige Leute: "Verschlüsse sind Objekte armer Männer." Aber wir könnten Objekte auch als Verschlüsse armer Männer betrachten, da wir erstklassige Funktionen wirklich mögen.

Ich benutze Schema, um die Ideen zu veranschaulichen, die aufgrund dieses Schemas eine der frühesten Sprachen sind, die echte Verschlüsse haben. Alle Materialien hier werden in SICP Kapitel 3 viel besser vorgestellt.

Zusammenfassend sind Lambda und Closure wirklich unterschiedliche Konzepte. Ein Lambda ist eine Funktion. Ein Verschluss ist ein Lambda-Paar und die entsprechende Umgebung, die das Lambda schließt.


Man könnte also alle Verschlüsse durch verschachtelte Lambdas ersetzen, bis keine freien Variablen mehr vorhanden sind? In diesem Fall würde ich sagen, dass Verschlüsse als besondere Art von Lambdas angesehen werden könnten.
Trilarion

8

Das Konzept ist das gleiche wie oben beschrieben, aber wenn Sie einen PHP-Hintergrund haben, wird dies anhand der Verwendung von PHP-Code näher erläutert.

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

Funktion ($ v) {return $ v> 2; } ist die Lambda-Funktionsdefinition. Wir können es sogar in einer Variablen speichern, damit es wiederverwendbar ist:

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

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

Was ist nun, wenn Sie die maximal zulässige Anzahl im gefilterten Array ändern möchten? Sie müssten eine andere Lambda-Funktion schreiben oder einen Abschluss erstellen (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));

Ein Abschluss ist eine Funktion, die in einer eigenen Umgebung ausgewertet wird und über eine oder mehrere gebundene Variablen verfügt, auf die beim Aufruf der Funktion zugegriffen werden kann. Sie stammen aus der Welt der funktionalen Programmierung, in der eine Reihe von Konzepten im Spiel sind. Verschlüsse sind wie Lambda-Funktionen, jedoch intelligenter in dem Sinne, dass sie mit Variablen aus der Außenumgebung interagieren können, in der der Verschluss definiert ist.

Hier ist ein einfacheres Beispiel für das Schließen von PHP:

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

$closure();

Schön erklärt in diesem Artikel.


7

Diese Frage ist alt und hat viele Antworten bekommen.
Mit Java 8 und Official Lambda, die inoffizielle Abschlussprojekte sind, wird die Frage wiederbelebt.

Die Antwort im Java-Kontext (über Lambdas und Closures - was ist der Unterschied? ):

"Ein Abschluss ist ein Lambda-Ausdruck, der mit einer Umgebung gepaart ist, die jede seiner freien Variablen an einen Wert bindet. In Java werden Lambda-Ausdrücke mithilfe von Abschlüssen implementiert, sodass die beiden Begriffe in der Community synonym verwendet werden."


Wie werden Lamdas durch Schließen in Java implementiert? Bedeutet dies, dass der Lamdas-Ausdruck in eine anonyme Klasse alten Stils konvertiert wird?
Hackjutsu

5

Einfach gesagt, das Schließen ist ein Trick über den Umfang, Lambda ist eine anonyme Funktion. Wir können einen Abschluss mit Lambda eleganter realisieren und Lambda wird oft als Parameter verwendet, der an eine höhere Funktion übergeben wird


4

Ein Lambda-Ausdruck ist nur eine anonyme Funktion. In einfachem Java können Sie es beispielsweise so schreiben:

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

Dabei wird die Klassenfunktion nur in Java-Code erstellt. Jetzt können Sie mapPersonToJob.apply(person)irgendwo anrufen , um es zu verwenden. das ist nur ein Beispiel. Das ist ein Lambda, bevor es eine Syntax dafür gab. Lambdas eine Abkürzung dafür.

Schließung:

Ein Lambda wird zu einem Abschluss, wenn er auf Variablen außerhalb dieses Bereichs zugreifen kann. Ich denke, man kann seine Magie sagen, es kann sich magisch um die Umgebung wickeln, in der es erstellt wurde, und die Variablen außerhalb seines Bereichs verwenden (äußerer Bereich. Um klar zu sein, bedeutet ein Abschluss, dass ein Lambda auf seinen AUSSENBEREICH zugreifen kann.

In Kotlin kann ein Lambda immer auf seinen Abschluss zugreifen (die Variablen, die sich in seinem äußeren Bereich befinden).


0

Dies hängt davon ab, ob eine Funktion eine externe Variable verwendet oder nicht, um eine Operation auszuführen.

Externe Variablen - Variablen, die außerhalb des Funktionsumfangs definiert sind.

  • Lambda-Ausdrücke sind zustandslos, da die Ausführung von Operationen von Parametern, internen Variablen oder Konstanten abhängt.

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • Closures halten den Status, da externe Variablen (dh Variablen, die außerhalb des Funktionsumfangs des Funktionskörpers definiert sind) zusammen mit Parametern und Konstanten zum Ausführen von Operationen verwendet werden.

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

Wenn Java einen Abschluss erstellt, behält es die Variable n mit der Funktion bei, sodass auf sie verwiesen werden kann, wenn sie an andere Funktionen übergeben oder irgendwo verwendet wird.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.