Es gibt viele SO-Posts, die damit in Zusammenhang stehen, aber ich frage das erneut mit einem anderen Zweck
Ich versuche zu verstehen, warum Schließungen wichtig und nützlich sind. Eines der Dinge, die ich in anderen SO-Posts gelesen habe, ist, dass wenn Sie eine Variable an Closure übergeben, der Closure ab diesem Zeitpunkt an diesen Wert erinnert. Ist das der ganze technische Aspekt davon oder gibt es mehr, was dort passiert.
Was ich mich dann wundere ist, was passieren würde, wenn die Variable, die innerhalb des Verschlusses verwendet wird, von außen verändert wird. Sollten sie nur Konstanten sein?
In der Sprache Clojure kann ich Folgendes tun: Aber da Werte unveränderlich sind, tritt dieses Problem nicht auf. Was ist mit anderen Sprachen und was ist die richtige technische Definition einer Schließung?
%Vor%Dies ist nicht die Art von Antwort, die hier aufsteht, aber ich möchte Sie herzlich dazu auffordern, die Antwort auf Ihre Frage zu finden, indem Sie Shriram Krishnamurthis (gratis!) (online!) Lehrbuch Ich werde das Buch sehr, sehr kurz umschreiben, indem ich die Entwicklung der klitzekleinen Dolmetscher zusammenfasse, durch die es dich führt:
Das ist der entscheidende Punkt: Sie implementieren dies, und dann entdecken Sie, dass es mit Substitution funktioniert, aber mit Umgebungen ist es kaputt. Um dies zu beheben, müssen Sie insbesondere sicherstellen, dass Sie der Umgebung, die bei der Auswertung vorhanden war, eine evaluierte Funktionsdefinition zuordnen. Dieses Paar (fundef + environment-of-definition) wird als "closure" bezeichnet.
Puh!
Okay, was passiert, wenn wir dem Bild veränderbare Bindungen hinzufügen? Wenn Sie dies selbst ausprobieren, werden Sie feststellen, dass die natürliche Implementierung eine Umgebung ersetzt, die Namen mit Werten in einer Umgebung verknüpft, die Namen Bindungen zuordnet. Dies ist orthogonal zum Begriff der Schließungen; Da Closures Umgebungen erfassen und Umgebungen jetzt Bindungen zuordnen, erhalten Sie das von Ihnen beschriebene Verhalten, wobei die Mutation einer in einer Umgebung erfassten Variablen sichtbar und beständig ist.
Noch einmal, ich möchte Sie dringend bitten, sich PLAI anzusehen.
Eine Closure ist eine Datenstruktur, die vom Compiler verwendet wird, um sicherzustellen, dass eine Funktion immer Zugriff auf die Daten hat, die sie benötigen. Hier ist ein Beispiel für eine Funktion, die bei der Definition aufzeichnet.
%Vor%Wenn diese Funktion definiert ist, wird eine Klasse erstellt und eine kleine Struktur (eine Closure) wird auf dem Heap zugewiesen, der den Wert von foo speichert. Die Klasse hat einen Zeiger darauf (oder es enthält es nicht sicher). Wenn Sie dies erneut ausführen, würde eine zweite Schließung zugeordnet werden, um diese andere foo zu halten. Wenn wir sagen "diese Funktion schließt über foo", meinen wir damit, dass es einen Verweis auf eine Striktur / Klasse / was auch immer hat, speichert den Zustand von foo zum Zeitpunkt der Erstellung. Der Grund, warum Sie etwas schließen müssen, ist, dass die Funktion, die es enthält, verschwindet, bevor die Daten verwendet werden. In diesem Fall wird das äußere (welches den Wert von foo enthält) enden und lange vor der Verwendung von foo verschwinden, so dass niemand mehr in der Lage sein wird, foo zu modifizieren. natürlich könnte foo jemandem einen ref einreichen, der es dann modifizieren könnte.
Ein lexikalischer Abschluss ist einer, in dem die eingeschlossenen Variablen (z. B. greeting-prefix
in Ihrem Beispiel) durch Verweis eingeschlossen sind. Der erstellte Abschluss erhält nicht einfach den Wert von greeting-prefix
zum Zeitpunkt der Erstellung, sondern erhält eine Referenz. Wenn greeting-prefix
nach dem Erstellen der Closure geändert wird, wird der neue Wert von der Closure bei jedem Aufruf verwendet.
In reinen funktionalen Sprachen ist das kein großer Unterschied, denn Werte werden nie geändert. Es spielt also keine Rolle, ob der Wert von greeting-prefix
in die Closure kopiert wird: Es gibt keinen möglichen Unterschied im Verhalten, der sich aus der Bezugnahme auf das Original gegenüber seiner Kopie ergeben könnte.
In "Imperativ-Sprachen-mit-Schließungen", wie C # und Java (über anonyme Klassen), muss eine Entscheidung darüber getroffen werden, ob die eingeschlossene Variable durch Wert oder durch Verweis eingeschlossen ist. In Java wird dieser Entscheidung vorgebeugt, indem nur final
-Variablen eingeschlossen werden, was eine funktionale Sprache nachahmt, die diese Variable betrifft. In C # glaube ich, dass es eine andere Sache ist.
Das Einschließen nach Wert vereinfacht die Implementierung: Die einzuschließende Variable ist oft auf dem Stapel vorhanden und wird daher zerstört, wenn die Funktion, die die Schließung konstruiert, zurückkehrt - das heißt, sie kann nicht durch Referenz eingeschlossen werden. Wenn Sie einen Enclosure-Verweis benötigen, können Sie solche Variablen identifizieren und sie bei jedem Aufruf dieser Funktion in einem Objekt reservieren. Dieses Objekt wird dann als Teil der Umgebung des Verschlusses aufbewahrt und muss so lange in Betrieb bleiben, wie alle Verschlüsse, die es verwenden, aktiv sind. (Ich weiß nicht, ob kompilierte Sprachen diese Technik direkt verwenden.)
Weitere Beschreibungen finden Sie zum Beispiel:
Common Lisp HyperSpec, 3.1.4 Closures und Lexical Binding
und
Common Lisp die Sprache, 2. Ausgabe, Kapitel 3., Umfang und Umfang
Sie können sich eine Schließung als "Umgebung" vorstellen, in der Namen an Werte gebunden sind. Diese Namen sind für die Schließung völlig privat, weshalb wir sagen, dass sie ihre Umgebung "schließt". Ihre Frage ist also nicht sinnvoll, da das "Äußere" die geschlossene Umgebung nicht beeinflussen kann. Ja, eine Schließung kann sich auf einen Namen in einer globalen Umgebung beziehen (mit anderen Worten, wenn ein Name verwendet wird, der nicht in seiner privaten, geschlossenen Umgebung gebunden ist), aber das ist eine andere Geschichte.
Wenn Sie möchten, können Sie sich eine Umgebung als Wörterbuch oder Hash-Tabelle vorstellen. Eine Schließung erhält ein eigenes kleines Wörterbuch, in dem Namen nachgeschlagen werden.
Sie könnten gerne lesen Auf Lambda, Capture und Mutabilität , die beschreibt, wie das funktioniert in C # und F # zum Vergleich.
Sehen Sie sich diesen Blogbeitrag an: ADTs in Clojure . Es zeigt eine schöne Anwendung von Closures auf das Problem der Datensperrung, so dass es ausschließlich über eine bestimmte Schnittstelle zugänglich ist (Rendering des Datentyps undurchsichtig).
Der Grundgedanke hinter dieser Art der Sperrung ist einfacher mit dem Gegenbeispiel illustriert, das huaiyuan in Common Lisp gepostet hat, als ich diese Antwort verfasste. Tatsächlich ist die Clojure-Version insofern interessant, als sie zeigt, dass das Problem einer geschlossenen Variable, die ihren Wert ändert, in Clojure auftaucht, wenn die Variable zufällig eine Instanz eines der Referenztypen enthält.
%Vor%Wie beim ursprünglichen Make-Greeter-Beispiel könnten Sie es so umschreiben (beachten Sie den deref / @):
%Vor%Dann können Sie damit personalisierte Grüße von den verschiedenen Betreibern verschiedener Bereiche einer Website rendern. : -)
%Vor%Sie können sich eine Schließung als eine denken "Umgebung", in der Namen sind an Werte gebunden. Diese Namen sind ganz privat für die Schließung, die Deshalb sagen wir, dass es sich "schließt" seine Umgebung. Also deine Frage ist nicht sinnvoll, dass die "draußen" kann nicht beeinflussen geschlossene Umgebung. Ja ein Schließung kann sich auf einen Namen in a beziehen globale Umgebung (mit anderen Worten, wenn Es verwendet einen Namen, der nicht gebunden ist seine private, geschlossene Umgebung), aber das ist eine andere Geschichte.
Ich vermute, dass die Frage war, ob solche Dinge in Sprachen möglich sind, die eine Mutation lokaler Variablen erlauben:
%Vor%Und - da sie offensichtlich möglich sind - denke ich, dass die Frage legitim ist.
Tags und Links clojure functional-programming closures lisp