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?
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?
Antworten:
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:
Dies führt zu einem Fehler, da die Umgebung
func
nicht geschlossen wirdanotherfunc
-h
ist undefiniert.func
schließt nur über die globale Umgebung. Das wird funktionieren:Denn hier
func
ist definiert inanotherfunc
und 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ßtanotherfunc
‚s - Umgebung und kann den Zugriff Variablen innerhalb von es. In Python 3.1+ funktioniert die Mutation auch, wenn dasnonlocal
Schlüsselwort verwendet wird .Ein weiterer wichtiger Punkt -
func
wird dieanotherfunc
Umgebung weiterhin schließen , auch wenn sie nicht mehr ausgewertet wirdanotherfunc
. Dieser Code funktioniert auch:Dies wird 10 drucken.
Wie Sie bemerken, hat dies nichts mit Lambda zu tun - es handelt sich um zwei verschiedene (obwohl verwandte) Konzepte.
quelle
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: ►
f x
.(Betrachten Sie es als einen Funktionsaufruf , bei dem
f
sich die Funktion befindet undx
der einzige Parameter ist.)λ
(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+2
Nimmt den Ausdruckx+2
und teilt mit, dass das Symbolx
in 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)7
wiex
im Unterausdruckx+2
des angewendeten Lambda ersetzt, so dass Sie erhalten7+2
, der sich dann9
durch 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:
und Sie können es sofort auf einen Parameter wie diesen anwenden:
oder Sie können diese anonyme Funktion (Lambda) in einer Variablen speichern:
f
Dadurch erhält es effektiv einen Namen , sodass Sie darauf verweisen und es später mehrmals aufrufen können, z.Aber du musstest es nicht benennen. Man könnte es sofort nennen:
In LISP werden Lambdas wie folgt hergestellt:
und Sie können ein solches Lambda aufrufen, indem Sie es sofort auf einen Parameter anwenden:
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 Symbolx
an dieλx.
vorangegangene Lambda-Abstraktion gebunden . Aber das andere Symboly
ist 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, wasy
bedeutet.Tatsächlich gilt das Gleiche für die beiden anderen Symbole
2
und+
. 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:
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 Symbolx
gebunden, während das Symboly
frei ist. Daher istopen
und kann der Ausdruck nicht ausgewertet werden, es sei denn, Sie sagen, wasy
bedeutet (und dasselbe mit+
und2
, die ebenfalls frei sind). Angenommen, Sie haben auch eine Umgebung wie diese: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: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:
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.
quelle
Wenn die meisten Menschen an Funktionen denken, denken sie an benannte Funktionen :
Diese werden natürlich beim Namen genannt:
Mit Lambda-Ausdrücken können Sie anonyme Funktionen haben :
Mit dem obigen Beispiel können Sie das Lambda über die Variable aufrufen, der es zugewiesen wurde:
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:
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:
Das scheint nicht viel zu sein, aber was wäre, wenn dies alles in einer anderen Funktion wäre und Sie an
incrementX
eine externe Funktion übergeben würden?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:
quelle
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.
quelle
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.
quelle
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
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
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:
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:
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.
quelle
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.
Funktion ($ v) {return $ v> 2; } ist die Lambda-Funktionsdefinition. Wir können es sogar in einer Variablen speichern, damit es wiederverwendbar ist:
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):
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:
Schön erklärt in diesem Artikel.
quelle
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? ):
quelle
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
quelle
Ein Lambda-Ausdruck ist nur eine anonyme Funktion. In einfachem Java können Sie es beispielsweise so schreiben:
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).
quelle
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.
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.
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.
quelle