Kann mir das jemand erklären? Ich verstehe die grundlegenden Konzepte dahinter, aber ich sehe oft, dass sie austauschbar verwendet werden, und das verwirrt mich.
Und wenn wir schon dabei sind, wie unterscheiden sie sich von einer normalen Funktion?
Kann mir das jemand erklären? Ich verstehe die grundlegenden Konzepte dahinter, aber ich sehe oft, dass sie austauschbar verwendet werden, und das verwirrt mich.
Und wenn wir schon dabei sind, wie unterscheiden sie sich von einer normalen Funktion?
A lambda ist einfach eine anonyme Funktion - eine Funktion, die ohne Namen definiert ist. In einigen Sprachen, wie z.B. Scheme, sind sie gleichwertig mit benannten Funktionen. In der Tat wird die Funktionsdefinition umgeschrieben, indem ein Lambda intern an eine Variable gebunden wird. In anderen Sprachen, wie Python, gibt es einige (eher unnötige) Unterscheidungen zwischen ihnen, aber ansonsten verhalten sie sich gleich.
A Verschluss ist jede Funktion, die schließt sich um die Umwelt in dem sie definiert wurde. Das bedeutet, dass er auf Variablen zugreifen kann, die nicht in seiner Parameterliste stehen. Beispiele:
def func(): return h
def anotherfunc(h):
return func()
Dies führt zu einem Fehler, da func
nicht zu Ende gehen die Umwelt in anotherfunc
- h
ist undefiniert. func
nur über die globale Umwelt geschlossen wird. Dies wird funktionieren:
def anotherfunc(h):
def func(): return h
return func()
Denn hier, func
ist definiert in anotherfunc
und in Python 2.3 und höher (oder einer ähnlichen Zahl), wenn sie fast Abschlüsse richtig verstanden hat (Mutation funktioniert immer noch nicht), bedeutet dies, dass es schließt sich um anotherfunc
Umgebung und kann auf Variablen innerhalb dieser Umgebung zugreifen. In Python 3.1+ funktioniert die Mutation auch, wenn man die nonlocal
Stichwort .
Ein weiterer wichtiger Punkt - func
wird auch in Zukunft über anotherfunc
Umgebung, auch wenn sie nicht mehr in anotherfunc
. Dieser Code wird auch funktionieren:
def anotherfunc(h):
def func(): return h
return func
print anotherfunc(10)()
Damit wird 10 gedruckt.
Dies hat, wie Sie feststellen werden, nichts zu tun mit lambda s - es handelt sich um zwei unterschiedliche (wenn auch verwandte) Konzepte.
Es gibt eine Menge Verwirrung um Lambdas und Closures, sogar in den Antworten auf diese StackOverflow-Frage hier. Anstatt zufällige Programmierer zu fragen, die über Closures aus der Praxis mit bestimmten Programmiersprachen gelernt haben, oder andere ahnungslose Programmierer, machen Sie eine Reise zum Quelle (wo alles begann). Und da Lambdas und Schließungen von Lambda-Kalkül von Alonzo Church in den 30er Jahren erfunden wurde, noch bevor es die ersten elektronischen Computer gab, ist dies der Quelle Ich spreche von.
Lambda Calculus ist die einfachste Programmiersprache der Welt. Die einzigen Dinge, die man in ihr tun kann:
f x
.f
ist die Funktion und x
ist sein einziger Parameter)x
), dann ein Punkt .
vor dem Ausdruck. Dies wandelt den Ausdruck in eine Funktion in Erwartung einer Parameter .x.x+2
nimmt den Ausdruck x+2
und sagt, dass das Symbol x
in diesem Ausdruck ist ein gebundene Variable - kann er durch einen Wert ersetzt werden, den Sie als Parameter angeben.(x.x+2) 7
. Dann wird der Ausdruck (in diesem Fall ein literaler Wert) 7
wird ersetzt durch x
in dem Teilausdruck x+2
des angewandten Lambdas, so dass man 7+2
was sich dann reduziert auf 9
nach den üblichen Rechenregeln.Wir haben also eines der Rätsel gelöst:
lambda es el 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 sie zum Beispiel 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; }
was ihm tatsächlich einen Namen gibt f
so dass Sie später mehrfach darauf verweisen und es aufrufen können, z. B.:
alert( f(7) + f(10) ); // should print 21 in the message box
Aber du hättest ihn nicht benennen müssen. Man konnte es sofort anrufen:
alert( function(x) { return x+2; } (7) ); // should print 9 in the message box
In LISP werden Lambdas auf diese Weise erstellt:
(lambda (x) (+ x 2))
und Sie können ein solches Lambda aufrufen, indem Sie es unmittelbar auf einen Parameter anwenden:
( (lambda (x) (+ x 2)) 7 )
OK, jetzt ist es an der Zeit, das andere Rätsel zu lösen: Was ist ein Verschluss . Zu diesem Zweck sollten wir über folgende Punkte sprechen Symbole ( Variablen ) in Lambda-Ausdrücken.
Wie gesagt, die Lambda-Abstraktion bewirkt Folgendes verbindlich ein Symbol in seinem Unterausdruck, so dass es zu einem ersetzbaren Parameter . Ein solches Symbol wird als gebunden . Was aber, wenn der Ausdruck noch andere Symbole enthält? Zum Beispiel: x.x/y+2
. In diesem Ausdruck wird das Symbol x
ist durch die Lambda-Abstraktion gebunden x.
davor. Aber das andere Symbol, y
ist nicht gebunden - es ist gratis . Wir wissen nicht, was es ist und woher es kommt, also wissen wir auch nicht, was es bedeutet und was Wert er darstellt, und daher können wir diesen Ausdruck nicht bewerten, bis wir herausgefunden haben, was y
bedeutet.
Das Gleiche gilt für die beiden anderen Symbole, 2
y +
. 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 ihm sagen müssen, was sie bedeuten, indem wir sie irgendwo definieren, z. B. in einer Bibliothek oder der Sprache selbst.
Sie können sich die gratis Symbole, die an anderer Stelle, außerhalb des Ausdrucks, in seinem "umgebenden Kontext" definiert sind, der als sein Umwelt . Die Umgebung könnte ein größerer Ausdruck sein, von dem dieser Ausdruck ein Teil ist (wie Qui-Gon Jinn sagte: "Es gibt immer einen größeren Fisch" ;) ), oder in einer Bibliothek oder in der Sprache selbst (als ein primitiv ).
So können wir Lambda-Ausdrücke in zwei Kategorien einteilen:
Sie können eine CLOSE öffnen Lambda-Ausdruck durch Angabe der Umwelt die alle diese freien Symbole definiert, indem sie sie an einige Werte bindet (die Zahlen, Strings, anonyme Funktionen aka Lambdas, was auch immer sein können ).
Und hier kommt die Verschluss Teil:
Die Verschluss eines Lambda-Ausdruck ist diese bestimmte Menge von Symbolen, die im äußeren Kontext (Umgebung) definiert sind und die Werte für die freie Symbole in diesem Ausdruck, wodurch sie nicht mehr frei sind. Es wird ein öffnen Lambda-Ausdruck, der noch einige "undefinierte" freie Symbole enthält, in eine geschlossen eine, die keine freien Symbole mehr hat.
Wenn Sie zum Beispiel den folgenden Lambda-Ausdruck haben: x.x/y+2
das Symbol x
gebunden ist, während das Symbol y
ist frei, daher lautet der Ausdruck open
und kann nicht bewertet werden, wenn Sie nicht sagen, was y
bedeutet (und dasselbe gilt für +
y 2
, die ebenfalls kostenlos sind). Aber nehmen Sie an, Sie haben auch eine Umwelt wie diese:
{ y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5 }
Diese Umwelt liefert Definitionen für alle "undefinierten" (freien) Symbole aus unserem Lambda-Ausdruck ( y
, +
, 2
), und mehrere zusätzliche Symbole ( q
, w
). Die Symbole, die wir definieren müssen, sind diese Teilmenge der Umgebung:
{ y: 3,
+: [built-in addition],
2: [built-in number] }
und genau das ist die Verschluss unseres Lambda-Ausdrucks :>
Mit anderen Worten, sie schließt einen offenen Lambda-Ausdruck. Dies ist der Ort, an dem der Name Verschluss Das ist der Grund, warum so viele Antworten in diesem Thread nicht ganz korrekt sind :P
Warum also irren sie sich? Warum sagen so viele von ihnen, dass Closures einige Datenstrukturen im Speicher sind, oder einige Eigenschaften der Sprachen, die sie verwenden, oder warum verwechseln sie Closures mit Lambdas? :P
Nun, die Marktschreier 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 "Closures", was eigentlich nur Lambdas sein sollten. Oder sie nennen "Closures" eine bestimmte Technik, die sie zur Implementierung des lexikalischen Scopings verwenden, d. h. der Tatsache, dass eine Funktion auf die Variablen zugreifen kann, die zum Zeitpunkt ihrer Definition in ihrem äußeren Bereich definiert waren. Oft wird gesagt, dass die Funktion diese Variablen "einschließt", d.h. sie in eine Datenstruktur einschließt, um zu verhindern, dass sie nach Beendigung der Ausführung der äußeren Funktion zerstört werden. Aber das ist nur erfunden Postfaktum "Volksetymologie" und Marketing, was die Sache nur noch verwirrender macht, weil jeder Sprachhändler seine eigene Terminologie verwendet.
Und es ist sogar noch schlimmer, weil in dem, was sie sagen, immer ein bisschen Wahrheit steckt, so dass man es nicht so einfach als falsch abtun kann :P Lass mich erklären:
Wenn Sie eine Sprache implementieren wollen, die Lambdas als Bürger erster Klasse verwendet, müssen Sie ihnen erlauben, Symbole zu verwenden, die in ihrem umgebenden Kontext definiert sind (d.h. freie Variablen in Ihren Lambdas zu verwenden). Und diese Symbole müssen auch dann noch 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 nicht mehr vorhanden ist, wenn die Funktion zurückkehrt. Damit ein Lambda so funktioniert, wie Sie es erwarten, müssen Sie daher all diese freien Variablen irgendwie aus dem äußeren Kontext "abfangen" und für später speichern, auch wenn der äußere Kontext nicht mehr vorhanden ist. Das heißt, Sie müssen die Verschluss Ihres Lambdas (all diese externen Variablen, die es verwendet) und speichern Sie sie an einem anderen Ort (entweder, indem Sie eine Kopie erstellen oder indem Sie im Voraus Platz für sie vorbereiten, irgendwo anders als auf dem Stack). Die tatsächliche Methode, mit der Sie dieses Ziel erreichen, ist ein "Implementierungsdetail" Ihrer Sprache. Wichtig ist hier die Verschluss das ist die Menge der freie Variablen von der Umwelt Ihres Lambdas, die irgendwo gespeichert werden müssen.
Es dauerte nicht allzu lange, bis die Leute anfingen, die tatsächliche Datenstruktur, die sie in den Implementierungen ihrer Sprache zur Implementierung von Closure verwenden, als "Closure" selbst zu bezeichnen. Die Struktur sieht normalerweise etwa so aus:
Closure {
[pointer to the lambda function's machine code],
[pointer to the lambda function's environment]
}
und diese Datenstrukturen werden als Parameter an andere Funktionen weitergegeben, von Funktionen zurückgegeben und in Variablen gespeichert, um Lambdas zu repräsentieren und ihnen den Zugriff auf die sie umgebende Umgebung sowie auf den in diesem Kontext auszuführenden Maschinencode zu ermöglichen. Aber das ist nur ein Weg (einer von vielen), um implementieren Verschluss, nicht die Abschluss selbst.
Wie ich oben erklärt habe, ist der Abschluss eines Lambda-Ausdrucks die Teilmenge der Definitionen in seiner Umgebung, die den in diesem Lambda-Ausdruck enthaltenen freien Variablen Werte geben, d.h. Schließen der Ausdruck (Drehen eines öffnen Lambda-Ausdruck, der noch nicht ausgewertet werden kann, in eine geschlossen Lambda-Ausdruck, der dann ausgewertet werden kann, da alle darin enthaltenen Symbole nun definiert sind).
Alles andere ist nur ein "Cargo-Kult" und "Voo-doo-Magie" von Programmierern und Sprachverkäufern, die die wahren Wurzeln dieser Begriffe nicht kennen.
Ich hoffe, das beantwortet Ihre Fragen. Wenn Sie aber noch weitere Fragen haben, können Sie diese gerne in den Kommentaren stellen, und ich werde versuchen, es besser zu erklären.
Die meisten Menschen denken bei Funktionen 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
Con Lambda-Ausdrücke können Sie haben anonyme Funktionen :
@foo = lambda() {return "This is returned from a function without a name";}
Im obigen Beispiel können Sie das Lambda über die Variable aufrufen, der es zugewiesen wurde:
foo();
Nützlicher als die Zuweisung von anonymen Funktionen an Variablen ist jedoch die Übergabe an oder von Funktionen höherer Ordnung, d. h. Funktionen, die andere Funktionen akzeptieren/zurückgeben. In vielen dieser Fälle ist die Benennung einer Funktion überflüssig:
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)});
A Verschluss kann eine benannte oder anonyme Funktion sein, ist aber als solche bekannt, wenn sie Variablen in dem Bereich, in dem die Funktion definiert ist, "abschließt", d.h. die Schließung bezieht sich weiterhin auf die Umgebung mit allen äußeren Variablen, die in der Schließung selbst verwendet werden. 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 das alles in einer anderen Funktion wäre und Sie die incrementX
auf eine externe Funktion?
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)
So erhält man in der funktionalen Programmierung zustandsabhängige Objekte. Da die Benennung von "incrementX" nicht erforderlich ist, können Sie in diesem Fall ein Lambda verwenden:
function foo()
{ @x = 0;
return lambda()
{ x = x + 1;
return x;
};
}
Nicht alle Abschlüsse sind Lambdas und nicht alle Lambdas sind Abschlüsse. Beide sind Funktionen, aber nicht notwendigerweise in der Art und Weise, wie wir es gewohnt sind.
Ein Lambda ist im Wesentlichen eine Funktion, die inline definiert wird, anstatt die Standardmethode der Funktionsdeklaration zu verwenden. Lambdas können häufig als Objekte weitergegeben werden.
Eine Schließung ist eine Funktion, die ihren umgebenden Zustand einschließt, indem sie auf Felder außerhalb ihres Körpers verweist. Der eingeschlossene Zustand bleibt bei allen Aufrufen der Schließung erhalten.
In einer objektorientierten Sprache werden Abschlüsse normalerweise durch Objekte bereitgestellt. Einige OO-Sprachen (z. B. C#) implementieren jedoch eine spezielle Funktionalität, die der Definition von Abschlüssen durch reine Objekte näher kommt. funktionale Sprachen (wie z.B. Lisp), die keine Objekte haben, um den Zustand einzuschließen.
Interessant ist, dass die Einführung von Lambdas und Closures in C# die funktionale Programmierung dem Mainstream näher bringt.
Es ist ganz einfach: Lambda ist ein Sprachkonstrukt, d.h. einfach eine Syntax für anonyme Funktionen; eine Closure ist eine Technik, um sie zu implementieren - oder jede andere erstklassige Funktion, egal ob benannt oder anonym.
Genauer gesagt, ist ein Abschluss die Art und Weise, wie eine erstklassige Funktion wird zur Laufzeit als ein Paar aus seinem "Code" und einer Umgebung dargestellt, die alle in diesem Code verwendeten nichtlokalen Variablen "schließt". Auf diese Weise sind diese Variablen auch dann noch zugänglich, wenn die äußeren Bereiche, aus denen sie stammen, bereits verlassen wurden.
Leider gibt es viele Sprachen, die Funktionen nicht als Werte erster Klasse oder nur in verkrüppelter Form unterstützen. Daher wird oft der Begriff "Closure" verwendet, um "das Echte" zu unterscheiden.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.