Warum sollte ein Programm einen Abschluss verwenden?

58

Nachdem ich hier viele Posts gelesen habe, in denen Schließungen erklärt werden, fehlt mir noch ein Schlüsselkonzept: Warum eine Schließung schreiben? Welche spezifische Aufgabe würde ein Programmierer ausführen, die durch einen Abschluss am besten bedient werden könnte?

Beispiele für Schließungen in Swift sind Zugriffe auf eine NSUrl und die Verwendung des Reverse Geocoders. Hier ist ein solches Beispiel. Leider präsentieren diese Kurse nur die Schließung; Sie erklären nicht, warum die Codelösung als Abschluss geschrieben ist.

Ein Beispiel für ein reales Programmierproblem, das mein Gehirn dazu veranlassen könnte, zu sagen: "Aha, ich sollte einen Abschluss dafür schreiben", wäre informativer als eine theoretische Diskussion. An theoretischen Diskussionen mangelt es auf dieser Seite nicht.

Bendrix
quelle
7
"Kürzlich überarbeitete Erklärung der Schließungen hier" - Fehlt Ihnen ein Link?
Dan Pichelman
2
Sie sollten diese Klarstellung über die asynchrone Aufgabe in einen Kommentar unterhalb der entsprechenden Antwort einfügen. Dies ist nicht Teil Ihrer ursprünglichen Frage, und der Beantworter wird niemals über Ihre Bearbeitung benachrichtigt.
Robert Harvey
5
Nur ein Leckerbissen: Der entscheidende Punkt bei Closures ist das lexikalische Scoping (im Gegensatz zu dynamischem Scoping), Closures ohne lexikalisches Scoping sind irgendwie nutzlos. Verschlüsse werden manchmal als lexikalische Verschlüsse bezeichnet.
Hoffmann,
1
Mit Closures können Sie Funktionen instanziieren .
user253751
1
Lesen Sie ein gutes Buch über funktionale Programmierung. Vielleicht mit Start SICP
Basile Starynkevitch

Antworten:

33

Erstens gibt es nichts, was ohne Verschlüsse nicht möglich wäre. Sie können einen Abschluss jederzeit durch ein Objekt ersetzen, das eine bestimmte Schnittstelle implementiert. Es ist nur eine Frage der Kürze und der reduzierten Kopplung.

Zweitens sollten Sie bedenken, dass Verschlüsse häufig unangemessen verwendet werden, wenn eine einfache Funktionsreferenz oder ein anderes Konstrukt klarer wäre. Sie sollten nicht jedes Beispiel als Best Practice betrachten.

Wo Verschlüsse wirklich über andere Konstrukte erstrahlen wird bei der Verwendung von Funktionen höherer Ordnung, wenn Sie tatsächlich benötigen Zustand zu kommunizieren, und Sie können es ein Einzeiler machen, wie in diesem JavaScript - Beispiel aus der Wikipedia - Seite über Schließungen :

// Return a list of all books with at least 'threshold' copies sold.
function bestSellingBooks(threshold) {
  return bookList.filter(
      function (book) { return book.sales >= threshold; }
    );
}

Hier thresholdwird sehr prägnant und natürlich kommuniziert, wo es definiert ist, wo es verwendet wird. Ihr Anwendungsbereich ist genau so klein wie möglich. filterEs muss nicht geschrieben werden, um die Möglichkeit zu ermöglichen, clientdefinierte Daten wie einen Schwellenwert zu übergeben. Wir müssen keine Zwischenstrukturen definieren, um die Schwelle in dieser einen kleinen Funktion zu kommunizieren. Es ist völlig in sich geschlossen.

Sie können dies ohne Abschluss schreiben, aber es wird viel mehr Code erfordern und schwieriger zu folgen sein. Außerdem hat JavaScript eine ziemlich ausführliche Lambda-Syntax. In Scala wäre zum Beispiel der gesamte Funktionskörper:

bookList filter (_.sales >= threshold)

Wenn Sie jedoch ECMAScript 6 verwenden können , wird dank der Fettpfeil-Funktionen sogar der JavaScript-Code viel einfacher und kann tatsächlich in eine einzelne Zeile gesetzt werden.

const bestSellingBooks = (threshold) => bookList.filter(book => book.sales >= threshold);

Suchen Sie in Ihrem eigenen Code nach Stellen, an denen Sie viel Boilerplate generieren, um temporäre Werte von einer Stelle zur anderen zu übertragen. Dies sind hervorragende Möglichkeiten, um einen Austausch durch einen Verschluss in Betracht zu ziehen.

Karl Bielefeldt
quelle
Wie genau reduzieren Verschlüsse die Kopplung?
SBICHENKO
Ohne Abschlüsse müssen Sie den bestSellingBooksCode und den filterCode einschränken , z. B. eine bestimmte Schnittstelle oder ein bestimmtes Benutzerdatenargument, um die thresholdDaten kommunizieren zu können. Das verbindet die beiden Funktionen auf viel weniger wiederverwendbare Weise.
Karl Bielefeldt,
51

Zur Erklärung leihe ich mir einen Code aus diesem hervorragenden Blog-Beitrag über Schließungen aus . Es ist JavaScript, aber das ist die Sprache, die die meisten Blog-Beiträge über Schließungen verwenden, weil Schließungen in JavaScript so wichtig sind.

Angenommen, Sie wollten ein Array als HTML-Tabelle rendern. Du könntest es so machen:

function renderArrayAsHtmlTable (array) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + object + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Sie sind jedoch dem JavaScript überlassen, wie jedes Element im Array gerendert wird. Wenn Sie das Rendern steuern möchten, können Sie dies tun:

function renderArrayAsHtmlTable (array, renderer) {
  var table = "<table>";
  for (var idx in array) {
    var object = array[idx];
    table += "<tr><td>" + renderer(object) + "</td></tr>";
  }
  table += "</table>";
  return table;
}

Und jetzt können Sie einfach eine Funktion übergeben, die das gewünschte Rendering zurückgibt.

Was wäre, wenn Sie in jeder Tabellenzeile eine laufende Summe anzeigen möchten? Sie würden eine Variable benötigen, um diese Summe zu verfolgen, nicht wahr? Mit einem Abschluss können Sie eine Renderer-Funktion schreiben, die über der Variablen für die laufende Summe endet, und Sie können einen Renderer schreiben, der die laufende Summe verfolgt:

function intTableWithTotals (intArray) {
  var total = 0;
  var renderInt = function (i) {
    total += i;
    return "Int: " + i + ", running total: " + total;
  };
  return renderObjectsInTable(intArray, renderInt);
}

Die Magie, die hier geschieht, besteht darin, renderInt den Zugriff auf die totalVariable beizubehalten, auch wenn sie renderIntwiederholt aufgerufen und beendet wird.

In einer traditionelleren objektorientierten Sprache als JavaScript können Sie eine Klasse schreiben, die diese Gesamtvariable enthält, und diese weitergeben, anstatt einen Abschluss zu erstellen. Aber ein Verschluss ist eine viel mächtigere, sauberere und elegantere Art, dies zu tun.

Weitere Lektüre

Robert Harvey
quelle
10
Im Allgemeinen kann man sagen, dass "erstklassige Funktion == Objekt mit nur einer Methode", "Abschluss == Objekt mit nur einer Methode und Status", "Objekt == Bündel von Abschlüssen".
Jörg W Mittag,
Sieht gut aus. Eine andere Sache, und diese Pedanterie sein könnte, ist , dass Javascript ist objektorientiert, und Sie könnten ein Objekt erstellen, das die Gesamt Variable enthält und dass rund passieren. Verschlüsse bleiben viel leistungsfähiger, sauberer und eleganter und sorgen auch für ein weitaus idiomatischeres Javascript. Die Art und Weise, wie sie geschrieben werden, könnte jedoch bedeuten, dass Javascript dies nicht objektorientiert ausführen kann.
KRyan,
2
Um wirklich pedantisch zu sein: Closures machen JavaScript überhaupt erst objektorientiert! Bei OO geht es um Datenabstraktion, und bei Closures wird die Datenabstraktion in JavaScript ausgeführt.
Jörg W Mittag
@ JörgWMittag: Wie Sie Cloures als Objekte mit nur einer Methode sehen können, können Sie Objekte als Auflistung von Closures sehen, die über denselben Variablen schließen: Die Mitgliedsvariablen eines Objekts sind nur die lokalen Variablen des Konstruktors des Objekts und die Methoden des Objekts Hierbei handelt es sich um Abschlüsse, die innerhalb des Gültigkeitsbereichs des Konstruktors definiert und zum späteren Aufrufen bereitgestellt werden. Die Konstruktorfunktion gibt eine Funktion höherer Ordnung (das Objekt) zurück, die bei jedem Abschluss entsprechend dem für den Aufruf verwendeten Methodennamen ausgelöst werden kann.
Giorgio,
@Giorgio: In der Tat werden Objekte in der Regel auf diese Weise in Scheme implementiert, und es ist auch so, wie Objekte in JavaScript implementiert werden (was angesichts der engen Beziehung zu Scheme nicht verwunderlich ist, schließlich wurde Brendan Eich ursprünglich beauftragt, eine zu entwerfen Scheme-Dialekt und Implementierung eines eingebetteten Scheme-Interpreters in Netscape Navigator. Erst später wurde ihm befohlen, eine Sprache mit Objekten zu erstellen, die wie C ++ aussehen. Danach nahm er nur noch minimale Änderungen vor, um diese Marketinganforderungen zu erfüllen.
Jörg W Mittag
22

Der Zweck von closuresist einfach, Zustand zu bewahren ; daher der Name closure- es schließt sich über Zustand. Zur leichteren Erklärung verwende ich Javascript.

Normalerweise haben Sie eine Funktion

function sayHello(){
    var txt="Hello";
    return txt;
}

Wobei der Gültigkeitsbereich der Variablen an diese Funktion gebunden ist. Nach der Ausführung verlässt die Variable den txtGültigkeitsbereich. Es gibt keine Möglichkeit, darauf zuzugreifen oder sie zu verwenden, nachdem die Ausführung der Funktion abgeschlossen ist.

Closures sind Sprachkonstrukte, mit denen - wie bereits gesagt - der Zustand der Variablen erhalten und damit der Geltungsbereich erweitert werden kann.

Dies kann in verschiedenen Fällen hilfreich sein. Ein Anwendungsfall ist die Konstruktion von Funktionen höherer Ordnung .

In Mathematik und Informatik ist eine Funktion höherer Ordnung (auch funktionale Form, Funktion oder Funktor) eine Funktion, die mindestens eine der folgenden Aufgaben erfüllt: 1

  • Nimmt eine oder mehrere Funktionen als Eingabe an
  • gibt eine Funktion aus

Ein einfaches, aber zugegebenermaßen nicht allzu nützliches Beispiel ist:

 makeadder=function(a){
     return function(b){
         return a+b;
     }
 }

 add5=makeadder(5);
 console.log(add5(10)); 

Sie definieren eine Funktion makedadder, die einen Parameter als Eingabe verwendet und eine Funktion zurückgibt . Es gibt eine äußerefunction(a){} und eine innere Funktion. function(b){}{}Außerdem definieren Sie (implizit) eine andere Funktion add5als Ergebnis des Aufrufs der Funktion höherer Ordnung makeadder. makeadder(5)gibt eine anonyme ( innere ) Funktion zurück, die wiederum 1 Parameter und die Summe der Parameter der äußeren Funktion und der Parameter der inneren Funktion zurückgibt .

Der Trick ist, dass bei der Rückgabe der inneren Funktion, die das eigentliche Hinzufügen vornimmt, der Gültigkeitsbereich des Parameters der äußeren Funktion ( a) erhalten bleibt. add5 erinnert sich , dass der Parameter awar 5.

Oder um ein zumindest irgendwie nützliches Beispiel zu zeigen:

  makeTag=function(openTag, closeTag){
     return function(content){
         return openTag +content +closeTag;
     }
 }

 table=makeTag("<table>","</table>")
 tr=makeTag("<tr>", "</tr>");
 td=makeTag("<td>","</td>");
 console.log(table(tr(td("I am a Row"))));

Ein weiterer häufiger Anwendungsfall ist der sogenannte IIFE = sofort aufgerufene Funktionsausdruck. In Javascript ist es sehr verbreitet, private Mitgliedsvariablen zu fälschen . Dies geschieht über eine Funktion, die einen privaten Gültigkeitsbereich = erzeugt closure, da dieser unmittelbar nach dem Definitionsaufruf aufgerufen wird. Die Struktur ist function(){}(). Beachten Sie die Klammern ()nach der Definition. Dies macht es möglich, es für die Objekterstellung mit aufschlussreichem Modulmuster zu verwenden . Der Trick besteht darin, einen Bereich zu erstellen und ein Objekt zurückzugeben, das nach Ausführung des IIFE Zugriff auf diesen Bereich hat.

Das Beispiel von Addi sieht so aus:

 var myRevealingModule = (function () {

         var privateVar = "Ben Cherry",
             publicVar = "Hey there!";

         function privateFunction() {
             console.log( "Name:" + privateVar );
         }

         function publicSetName( strName ) {
             privateVar = strName;
         }

         function publicGetName() {
             privateFunction();
         }


         // Reveal public pointers to
         // private functions and properties

         return {
             setName: publicSetName,
             greeting: publicVar,
             getName: publicGetName
         };

     })();

 myRevealingModule.setName( "Paul Kinlan" );

Das zurückgegebene Objekt enthält Verweise auf Funktionen (z. B. publicSetName), die wiederum Zugriff auf "private" Variablen haben privateVar.

Dies sind jedoch speziellere Anwendungsfälle für Javascript.

Welche spezifische Aufgabe würde ein Programmierer ausführen, die durch einen Abschluss am besten bedient werden könnte?

Dafür gibt es mehrere Gründe. Man könnte sein, dass es für ihn selbstverständlich ist, da er einem funktionalen Paradigma folgt . Oder in Javascript: Es ist nur die Notwendigkeit , sich auf Verschlüsse zu verlassen, um einige Macken der Sprache zu umgehen.

Thomas Junk
quelle
"Der Zweck von Verschlüssen ist einfach, den Zustand zu bewahren, daher der Name Verschluss - er schließt über dem Zustand.": Es kommt wirklich auf die Sprache an. Schließungen werden über externen Namen / Variablen geschlossen. Diese können den Status (Speicherorte, die geändert werden können), aber auch Werte (unveränderlich) angeben. Closures in Haskell bewahren den Status nicht auf: Sie bewahren Informationen auf, die zum Zeitpunkt und in dem Kontext bekannt waren, in dem der Closure erstellt wurde.
Giorgio,
1
»Sie bewahren Informationen auf, die zum Zeitpunkt und in dem Kontext bekannt waren, in dem die Schließung erstellt wurde.« Unabhängig davon, ob sie geändert werden konnten oder nicht, handelt es sich um einen Zustand - vielleicht einen unveränderlichen Zustand .
Thomas Junk
16

Es gibt zwei Hauptanwendungsfälle für Verschlüsse:

  1. Asynchronität. Angenommen, Sie möchten eine Aufgabe ausführen, die eine Weile dauert, und dann etwas tun, wenn sie erledigt ist. Sie können entweder Ihren Code warten lassen, bis er fertig ist, wodurch die weitere Ausführung blockiert wird und Ihr Programm möglicherweise nicht mehr reagiert, oder Sie können Ihre Aufgabe asynchron aufrufen und sagen: "Beginnen Sie diese lange Aufgabe im Hintergrund, und führen Sie diese Beendigung aus, wenn sie abgeschlossen ist." Dabei enthält der Abschluss den Code, der ausgeführt werden soll, wenn er fertig ist.

  2. Rückrufe. Diese werden je nach Sprache und Plattform auch als "Delegates" oder "Event-Handler" bezeichnet. Die Idee ist, dass Sie ein anpassbares Objekt haben, das an bestimmten genau definierten Punkten ein Ereignis ausführt , das einen Abschluss ausführt , der von dem Code übergeben wird, der es eingerichtet hat. In der Benutzeroberfläche Ihres Programms haben Sie möglicherweise eine Schaltfläche, die den Code enthält, der ausgeführt werden soll, wenn der Benutzer auf die Schaltfläche klickt.

Es gibt mehrere andere Verwendungszwecke für Verschlüsse, aber dies sind die beiden Hauptzwecke.

Mason Wheeler
quelle
23
Also im Grunde Rückrufe dann, da das erste Beispiel auch ein Rückruf ist.
Robert Harvey
2
@RobertHarvey: Technisch gesehen, aber sie sind verschiedene mentale Modelle. Beispielsweise erwarten Sie (im Allgemeinen), dass der Ereignishandler mehrmals aufgerufen wird, Ihre asynchrone Fortsetzung jedoch nur einmal. Aber ja, technisch gesehen ist alles, was Sie mit einem Abschluss tun würden, ein Rückruf. (Es sei denn, Sie konvertieren es in einen Ausdrucksbaum, aber das ist eine ganz andere Sache.)
Mason Wheeler
4
@RobertHarvey: Das Betrachten von Abschlüssen als Rückrufe versetzt Sie in eine Stimmung, die Sie wirklich davon abhält, sie effektiv zu nutzen. Verschlüsse sind Rückrufe auf Steroide.
gnasher729
13
@ MasonWheeler Sagen wir es so, es klingt falsch. Rückrufe haben nichts mit Schließungen zu tun; Natürlich können Sie eine Funktion innerhalb eines Abschlusses zurückrufen oder einen Abschluss als Rückruf aufrufen. Ein Rückruf ist aber kein Abschluss notwendig. Ein Rückruf ist nur ein einfacher Funktionsaufruf. Ein Eventhandler ist an sich kein Abschluss. Für einen Delegaten ist kein Abschluss erforderlich: Es handelt sich in erster Linie um C # -Funktionszeiger. Natürlich können Sie damit einen Verschluss konstruieren. Ihre Erklärung ist ungenau. Es geht darum, den Zustand zu überwinden und ihn zu nutzen.
Thomas Junk
5
Vielleicht gibt es hier JS-spezifische Konnotationen, die mich missverstehen lassen, aber diese Antwort klingt für mich absolut falsch. Asynchronität oder Rückrufe können alles verwenden, was "aufrufbar" ist. Eine Schließung ist für beide nicht erforderlich - eine reguläre Funktion ist ausreichend. In der Zwischenzeit ist eine Schließung, wie sie in der funktionalen Programmierung definiert oder in Python verwendet wird, nützlich, da sie einen Zustand, dh eine Variable, "verschließt" und einer Funktion in einem inneren Bereich einen konsistenten Zugriff auf diese Variablen gewährt Wert, auch wenn die Funktion mehrmals aufgerufen und beendet wird.
Jonathan Hartley
13

Einige andere Beispiele:

Sortieren
Die meisten Sortierfunktionen arbeiten mit dem Vergleichen von Objektpaaren. Eine Vergleichstechnik ist erforderlich. Den Vergleich auf einen bestimmten Operator zu beschränken, bedeutet eine ziemlich unflexible Art. Ein viel besserer Ansatz ist es, eine Vergleichsfunktion als Argument für die Sortierfunktion zu erhalten. Manchmal funktioniert eine zustandslose Vergleichsfunktion (z. B. Sortieren einer Liste von Zahlen oder Namen), aber was ist, wenn der Vergleich den Status benötigt?

Sie können beispielsweise eine Liste der Städte nach Entfernung zu einem bestimmten Ort sortieren. Eine hässliche Lösung besteht darin, die Koordinaten dieses Ortes in einer globalen Variablen zu speichern. Dies macht die Vergleichsfunktion selbst zustandslos, jedoch auf Kosten einer globalen Variablen.

Dieser Ansatz schließt aus, dass mehrere Threads dieselbe Liste von Städten nach ihrer Entfernung zu zwei verschiedenen Standorten gleichzeitig sortieren. Ein Abschluss, der den Speicherort einschließt, löst dieses Problem und macht eine globale Variable überflüssig.


Zufallszahlen
Das Original rand()hat keine Argumente. Pseudozufallszahlengeneratoren benötigen einen Zustand. Einige (zB Mersenne Twister) brauchen viel Staat. Sogar der einfache, aber fürchterlich rand()notwendige Staat. Wenn Sie ein Mathejournal über einen neuen Zufallszahlengenerator lesen, sehen Sie zwangsläufig globale Variablen. Das ist schön für die Entwickler der Technik, nicht so schön für die Anrufer. Das Einkapseln dieses Zustands in eine Struktur und die Übergabe der Struktur an den Zufallszahlengenerator ist eine Möglichkeit, das globale Datenproblem zu umgehen. Dies ist der Ansatz, der in vielen Nicht-OO-Sprachen verwendet wird, um einen Zufallszahlengenerator wiedereintrittsfähig zu machen. Eine Schließung verbirgt diesen Zustand vor dem Anrufer. Ein Closure bietet die einfache Aufrufsequenz rand()und die Wiedereintrittsmöglichkeit des gekapselten Zustands.

Zufallszahlen sind mehr als nur ein PRNG. Die meisten Leute, die Zufälligkeit wollen, wollen, dass sie auf eine bestimmte Weise verteilt wird. Ich beginne mit zufällig gezogenen Zahlen zwischen 0 und 1, oder kurz U (0,1). Jedes PRNG, das Ganzzahlen zwischen 0 und einem bestimmten Maximum generiert, ist ausreichend. Teilen Sie einfach (als Gleitkomma) die zufällige ganze Zahl durch das Maximum. Eine bequeme und allgemeine Möglichkeit, dies zu implementieren, besteht darin, einen Abschluss zu erstellen, der einen Abschluss (den PRNG) und das Maximum als Eingaben verwendet. Jetzt haben wir einen generischen und einfach zu verwendenden Zufallsgenerator für U (0,1).

Neben U (0,1) gibt es eine Reihe weiterer Verteilungen. Zum Beispiel eine Normalverteilung mit einem bestimmten Mittelwert und einer bestimmten Standardabweichung. Jeder normale Verteilungsgenerator-Algorithmus, auf den ich gestoßen bin, verwendet einen U (0,1) Generator. Eine bequeme und generische Möglichkeit, einen normalen Generator zu erstellen, besteht darin, einen Verschluss zu erstellen, der den U (0,1) -Generator, den Mittelwert und die Standardabweichung als Status enthält. Dies ist zumindest konzeptionell ein Abschluss, bei dem ein Abschluss als Argument verwendet wird.

David Hammen
quelle
7

Closures entsprechen Objekten, die eine run () -Methode implementieren, und umgekehrt können Objekte mit Closures emuliert werden.

  • Der Vorteil von Verschlüssen besteht darin, dass sie problemlos überall dort eingesetzt werden können, wo Sie eine Funktion erwarten: auch Funktionen höherer Ordnung, einfache Rückrufe (oder Strategiemuster). Sie müssen keine Schnittstelle / Klasse definieren, um Ad-hoc-Abschlüsse zu erstellen.

  • Der Vorteil von Objekten ist die Möglichkeit komplexerer Interaktionen: mehrere Methoden und / oder verschiedene Schnittstellen.

Das Verwenden von Verschlüssen oder Objekten ist also meistens eine Frage des Stils. Im Folgenden finden Sie ein Beispiel für Dinge, die das Schließen von Objekten vereinfachen, deren Implementierung jedoch unpraktisch ist:

 (let ((seen))
    (defun register-name (name)
       (pushnew name seen :test #'string=))

    (defun all-names ()
       (copy-seq seen))

    (defun reset-name-registry ()
       (setf seen nil)))

Grundsätzlich kapseln Sie einen verborgenen Zustand, auf den nur über globale Closures zugegriffen wird: Sie müssen sich nicht auf ein Objekt beziehen, sondern verwenden nur das von den drei Funktionen definierte Protokoll.


Ich vertraue dem ersten Kommentar von supercat, dass es in einigen Sprachen möglich ist, die Lebensdauer von Objekten genau zu steuern, während dies bei Verschlüssen nicht der Fall ist. Im Fall von Sprachen mit Speicherbereinigung ist die Lebensdauer von Objekten jedoch im Allgemeinen unbegrenzt, und es ist daher möglich, einen Abschluss zu erstellen, der in einem dynamischen Kontext aufgerufen werden könnte, in dem er nicht aufgerufen werden sollte (Lesen aus einem Abschluss nach einem Stream) geschlossen ist, zum Beispiel).

Es ist jedoch recht einfach, einen solchen Missbrauch zu verhindern, indem eine Steuervariable erfasst wird, die die Ausführung eines Schließvorgangs schützt. Genauer gesagt, hier ist, was ich vorhabe (in Common Lisp):

(defun guarded (function)
  (let ((active t))
    (values (lambda (&rest args)
              (when active
                (apply function args)))
            (lambda ()
              (setf active nil)))))

Hier nehmen wir einen Funktionsbezeichner functionund geben zwei Abschlüsse zurück, die beide eine lokale Variable mit dem Namen erfassen active:

  • der erste delegiert an function, nur wenn activees wahr ist
  • der zweite setzt actionauf nil, aka false.

Stattdessen (when active ...)ist es natürlich möglich, einen (assert active)Ausdruck zu haben , der eine Ausnahme auslösen kann, falls der Abschluss aufgerufen wird, wenn dies nicht der Fall sein sollte. Denken Sie auch daran, dass der unsichere Code bei unsachgemäßer Verwendung möglicherweise bereits eine Ausnahme auslöst, sodass Sie einen solchen Wrapper selten benötigen.

So würden Sie es verwenden:

(use-package :metabang-bind) ;; for bind

(defun example (obj1 obj2)
  (bind (((:values f f-deactivator)(guarded (lambda () (do-stuff obj1))))
         ((:values g g-deactivator)(guarded (lambda () (do-thing obj2)))))

    ;; ensure the closure are inactive when we exit
    (unwind-protect
         ;; pass closures to other functions
         (progn
           (do-work f)
           (do-work g))

      ;; cleanup code: deactivate closures
      (funcall f-deactivator)
      (funcall g-deactivator))))

Beachten Sie, dass die deaktivierenden Verschlüsse auch anderen Funktionen zugewiesen werden können. Hier werden die lokalen activeVariablen nicht zwischen fund geteilt g. auch, zusätzlich zu active, fbezieht sich nur auf obj1und gnur bezieht sich auf obj2.

Der andere Punkt, den Supercat anführt, ist, dass Verschlüsse zu Speicherlecks führen können, aber leider ist dies in Umgebungen, in denen Müll gesammelt wird, für fast alles der Fall. Wenn sie verfügbar sind, kann dies durch schwache Zeiger behoben werden (der Abschluss selbst wird möglicherweise im Speicher gehalten, verhindert jedoch nicht die Speicherbereinigung anderer Ressourcen).

Core-Dump
quelle
1
Ein Nachteil von üblicherweise implementierten Verschlüssen ist, dass, sobald ein Verschluss, der eine lokale Variable einer Methode verwendet, der Außenwelt ausgesetzt ist, die Methode, die den Verschluss definiert, möglicherweise die Kontrolle darüber verliert, wann diese Variable gelesen und geschrieben wird. In den meisten Sprachen gibt es keine bequeme Möglichkeit, einen ephemeren Abschluss zu definieren, ihn an eine Methode zu übergeben und zu gewährleisten, dass er nicht mehr existiert, sobald die Methode, die den Abschluss erhalten hat, zurückgegeben wurde stellen Sie sicher, dass ein Closure nicht in einem unerwarteten Threading-Kontext ausgeführt wird.
Supercat
@supercat: Schauen Sie sich Objective-C und Swift an.
gnasher729
1
@supercat Gibt es nicht den gleichen Nachteil bei statusbehafteten Objekten?
Andres F.
@AndresF .: Es ist möglich, ein statusbehaftetes Objekt in einem Wrapper zu kapseln, der die Ungültigmachung unterstützt. Wenn Code eine privat gehaltene List<T>Klasse in einer (hypothetischen) Klasse einschließt TemporaryMutableListWrapper<T>und diese einem externen Code aussetzt, kann sichergestellt werden, dass der externe Code keine Möglichkeit mehr hat, den Wrapper zu manipulieren, wenn er ungültig wird List<T>. Man kann Verschlüsse entwerfen, um die Ungültigmachung zuzulassen, sobald sie ihren erwarteten Zweck erfüllt haben, aber es ist kaum praktisch. Es gibt Verschlüsse, um bestimmte Muster bequemer zu machen, und der Aufwand, sie zu schützen, würde dies zunichte machen.
Supercat
1
@coredump: Wenn in C # zwei Closures Variablen gemeinsam haben, wird dasselbe vom Compiler generierte Objekt für beide verwendet, da Änderungen an einer gemeinsam genutzten Variablen, die von einem Closure vorgenommen wurden, vom anderen gesehen werden müssen. Um dies zu vermeiden, müsste jeder Abschluss ein eigenes Objekt sein, das seine eigenen nicht freigegebenen Variablen enthält, sowie ein Verweis auf ein freigegebenes Objekt, das die freigegebenen Variablen enthält. Nicht unmöglich, aber es hätte den meisten variablen Zugriffen eine zusätzliche Dereferenzierungsstufe verliehen, die alles verlangsamt.
Supercat
6

Nichts, was noch nicht gesagt wurde, aber vielleicht ein einfacheres Beispiel.

Hier ist ein JavaScript-Beispiel mit Timeouts:

// Example function that logs something to the browser's console after a given delay
function delayedLog(message, delay) {
  // this function will be called when the timer runs out
  var fire = function () {
    console.log(message); // closure magic!
  };

  // set a timeout that'll call fire() after a delay
  setTimeout(fire, delay);
}

Was hier passiert, ist, dass wenn delayedLog()aufgerufen wird, es sofort nach dem Einstellen des Timeouts zurückkehrt und das Timeout im Hintergrund weiter nach unten läuft.

Wenn das Zeitlimit abgelaufen ist und die fire()Funktion aufgerufen wird, zeigt die Konsole die messageursprünglich übergebene an delayedLog(), da sie weiterhin fire()über Closure verfügbar ist . Sie können delayedLog()so viele Anrufe tätigen, wie Sie möchten, jedes Mal mit einer anderen Nachricht und Verzögerung, und es wird das Richtige tun.

Stellen wir uns aber vor, JavaScript hat keine Closures.

Eine Möglichkeit wäre, das setTimeout()Blockieren - eher wie eine "Schlaf" -Funktion - so zu gestalten delayedLog(), dass der Gültigkeitsbereich erst nach Ablauf des Timeouts verschwindet. Aber alles zu blockieren ist nicht sehr schön.

Eine andere Möglichkeit wäre, die messageVariable in einen anderen Bereich zu verschieben, auf den zugegriffen werden kann, nachdem delayedLog()der Bereich verlassen wurde.

Sie könnten globale - oder zumindest "umfassendere" - Variablen verwenden, aber Sie müssten herausfinden, wie Sie verfolgen können, welche Nachricht mit welchem ​​Timeout einhergeht. Es kann jedoch nicht nur eine sequentielle FIFO-Warteschlange sein, da Sie jede gewünschte Verzögerung einstellen können. Es könnte also "first in, third out" oder so sein. Sie benötigen also ein anderes Mittel, um eine zeitgesteuerte Funktion an die benötigten Variablen zu binden.

Sie können ein Timeout-Objekt instanziieren, das den Timer mit der Nachricht "gruppiert". Der Kontext eines Objekts ist mehr oder weniger ein Bereich, der sich nicht ändert. Dann müsste der Timer im Kontext des Objekts ausgeführt werden, damit es Zugriff auf die richtige Nachricht hat. Sie müssten dieses Objekt jedoch speichern, da es ohne Verweise zu einer Müllsammlung führen würde (ohne Verschlüsse gäbe es auch keine impliziten Verweise darauf). Und Sie müssten das Objekt entfernen, sobald es abgelaufen ist, sonst bleibt es einfach hängen. Sie benötigen also eine Art Liste mit Timeout-Objekten und überprüfen diese regelmäßig auf zu entfernende "verbrauchte" Objekte. Andernfalls werden die Objekte automatisch zur Liste hinzugefügt und daraus entfernt.

Also ... ja, das wird langweilig.

Zum Glück müssen Sie keinen breiteren Bereich verwenden oder Objekte streiten, um bestimmte Variablen beizubehalten. Da JavaScript über Abschlüsse verfügt, haben Sie bereits genau den Umfang, den Sie benötigen. Ein Bereich, mit dem Sie messagebei Bedarf auf die Variable zugreifen können. Und aus diesem Grund können Sie mit dem Schreiben delayedLog()wie oben davonkommen .

Flambino
quelle
Ich habe ein Problem damit, das als Schließung zu bezeichnen . Vielleicht würde ich es versehentlich schließen . messageist im Funktionsumfang von enthalten fireund wird daher in weiteren Aufrufen erwähnt; aber es tut , dass aus Versehen so zu sagen. Technisch ist es eine Schließung. +1 sowieso;)
Thomas Junk
@ThomasJunk Ich bin mir nicht sicher, ob ich ganz folge. Wie würde ein „ non -accidental closure“ aussehen? Ich habe oben Ihr makeadderBeispiel gesehen, das für mich ähnlich aussieht. Sie geben eine "Curry" -Funktion zurück, die ein einzelnes Argument anstelle von zwei nimmt; Mit den gleichen Mitteln erstelle ich eine Funktion, die keine Argumente akzeptiert. Ich gebe es nur nicht zurück, sondern weiter setTimeout.
Flambino
»Ich gebe es einfach nicht zurück«, vielleicht ist das der Punkt, der für mich den Unterschied ausmacht. Ich kann mein "Anliegen" nicht ganz ausdrücken;) In technischer Hinsicht hast du 100% recht. Durch Referenzieren messagein firewird der Abschluss generiert. Und wenn es aufgerufen setTimeoutwird, nutzt es den konservierten Zustand.
Thomas Junk
1
@ThomasJunk Ich sehe, wie es ein bisschen anders riechen könnte. Ich kann Ihre Besorgnis jedoch nicht teilen :) Oder ich würde es auf jeden Fall nicht "zufällig" nennen - ziemlich sicher, dass ich es absichtlich so codiert habe;)
Flambino
3

PHP kann verwendet werden, um ein reales Beispiel in einer anderen Sprache zu zeigen.

protected function registerRoutes($dic)
{
  $router = $dic['router'];

  $router->map(['GET','OPTIONS'],'/api/users',function($request,$response) use ($dic)
  {
    $controller = $dic['user_api_controller'];
    return $controller->findAllAction($request,$response);
  })->setName('api_users');
}

Im Grunde registriere ich eine Funktion, die für die / api / users- URI ausgeführt wird . Dies ist eigentlich eine Middleware-Funktion, die auf einem Stapel gespeichert wird. Andere Funktionen werden darum gewickelt. Ganz ähnlich wie bei Node.js / Express.js .

Der Abhängigkeitsinjektionscontainer ist (über die use-Klausel) in der Funktion verfügbar, wenn er aufgerufen wird. Es ist möglich, eine Art Routenaktionsklasse zu erstellen, aber dieser Code erweist sich als einfacher, schneller und leichter zu warten.

Cerad
quelle
-1

Ein Closure ist ein beliebiger Code, einschließlich Variablen, der als erstklassige Daten behandelt werden kann.

Ein triviales Beispiel ist guter alter Qsort: Es ist eine Funktion zum Sortieren von Daten. Sie müssen ihm einen Zeiger auf eine Funktion geben, die zwei Objekte vergleicht. Sie müssen also eine Funktion schreiben. Diese Funktion muss möglicherweise parametrisiert werden, dh, Sie geben ihr statische Variablen. Das heißt, es ist nicht threadsicher. Sie sind in DS. Sie schreiben also eine Alternative, die einen Abschluss anstelle eines Funktionszeigers akzeptiert. Sie lösen das Problem der Parametrisierung sofort, da die Parameter Teil des Abschlusses werden. Sie verbessern die Lesbarkeit Ihres Codes, indem Sie schreiben, wie Objekte direkt mit dem Code verglichen werden , der die Sortierfunktion aufruft.

Es gibt Unmengen von Situationen, in denen Sie eine Aktion ausführen möchten, die viel Code für die Kesselplatte sowie einen winzigen, aber wesentlichen Code erfordert, der angepasst werden muss. Sie vermeiden den Standardcode durch eine Funktion zu schreiben einmal , dass ein Parameter Schließung nimmt und macht die ganze vorformulierten Code um ihn herum, und dann können Sie diese Funktion aufrufen und den Code übergeben als Verschluss angepasst werden. Eine sehr kompakte und lesbare Art, Code zu schreiben.

Sie haben eine Funktion, bei der nicht-trivialer Code in vielen verschiedenen Situationen ausgeführt werden muss. Dies wurde verwendet, um entweder Code-Duplizierung oder verzerrten Code zu erzeugen, so dass der nicht-triviale Code nur einmal vorhanden war. Trivial: Sie weisen einer Variablen einen Abschluss zu und rufen ihn am besten auf, wo immer er benötigt wird.

Multithreading: iOS / MacOS X verfügt über Funktionen wie "Schließen eines Hintergrund-Threads", "... des Haupt-Threads", "... des Haupt-Threads in 10 Sekunden". Es macht Multithreading trivial .

Asynchrone Aufrufe: Das hat das OP gesehen. Jeder Anruf, der auf das Internet zugreift, oder alles andere, was Zeit in Anspruch nimmt (wie das Lesen von GPS-Koordinaten), ist etwas, auf das Sie nicht warten können. Sie haben also Funktionen, die Dinge im Hintergrund erledigen, und übergeben dann einen Abschluss, um ihnen mitzuteilen, was zu tun ist, wenn sie fertig sind.

Das ist ein kleiner Anfang. Fünf Situationen, in denen Verschlüsse revolutionär sind, wenn es darum geht, kompakten, lesbaren, zuverlässigen und effizienten Code zu erstellen.

gnasher729
quelle
-4

Ein Closure ist eine Kurzform, um eine Methode dort zu schreiben, wo sie angewendet werden soll. Es erspart Ihnen den Aufwand, eine separate Methode zu deklarieren und zu schreiben. Dies ist nützlich, wenn die Methode nur einmal verwendet wird und die Methodendefinition kurz ist. Die Vorteile verringern sich bei der Eingabe, da der Name der Funktion, ihr Rückgabetyp oder ihr Zugriffsmodifikator nicht angegeben werden müssen. Außerdem müssen Sie beim Lesen des Codes nicht nach der Definition der Methode suchen.

Das Obige ist eine Zusammenfassung von Understand Lambda Expressions von Dan Avidar.

Dies hat für mich die Verwendung von Verschlüssen verdeutlicht, da es die Alternativen (Verschluss vs. Methode) und die Vorteile der einzelnen klarstellt.

Der folgende Code wird einmal und nur einmal während der Installation verwendet. Wenn Sie es unter viewDidLoad an die richtige Stelle schreiben, sparen Sie sich die Suche an anderer Stelle und verringern die Größe des Codes.

myPhoton!.getVariable("Temp", completion: { (result:AnyObject!, error:NSError!) -> Void in
  if let e = error {
    self.getTempLabel.text = "Failed reading temp"
  } else {
    if let res = result as? Float {
    self.getTempLabel.text = "Temperature is \(res) degrees"
    }
  }
})

Darüber hinaus wird ein asynchroner Prozess ausgeführt, ohne dass andere Teile des Programms blockiert werden. Bei einem Abschluss bleibt ein Wert erhalten, der bei nachfolgenden Funktionsaufrufen wiederverwendet werden kann.

Ein weiterer Abschluss; Dieser erfasst einen Wert ...

let animals = ["fish", "cat", "chicken", "dog"]
let sortedStrings = animals.sorted({ (one: String, two: String) -> Bool in return one > two
}) println(sortedStrings)
Bendrix
quelle
4
Leider verwechselt dies Verschlüsse und Lambdas. Lambdas verwenden häufig Closures, da sie in der Regel weitaus nützlicher sind, wenn sie a) sehr knapp und b) innerhalb und abhängig von einem bestimmten Methodenkontext, einschließlich Variablen, definiert sind. Der eigentliche Abschluss hat jedoch nichts mit der Idee eines Lambda zu tun, dh der Fähigkeit, eine erstklassige Funktion zu definieren und diese für die spätere Verwendung weiterzugeben.
Nathan Tuggy
Lesen Sie diese, während Sie diese Antwort ablehnen ... Wann sollte ich ablehnen? Verwenden Sie Ihre Abstimmungen immer dann, wenn Sie auf einen ungeheuer schlampigen, mühelosen Beitrag oder eine Antwort stoßen, die eindeutig und möglicherweise gefährlich falsch ist. Meiner Antwort fehlen möglicherweise einige Gründe für die Verwendung eines Verschlusses, aber sie ist weder ungeheuer schlampig noch gefährlich falsch. Es gibt viele Artikel und Diskussionen über Abschlüsse, die sich mit funktionaler Programmierung und Lambda-Notation befassen. Alles, was ich antworte, ist, dass diese Erklärung der funktionalen Programmierung mir das Verständnis von Abschlüssen erleichtert hat. Das und der bleibende Wert.
Bendrix
1
Die Antwort ist vielleicht nicht gefährlich , aber mit Sicherheit "eindeutig [...] falsch". Es werden die falschen Begriffe für Konzepte verwendet, die sich in hohem Maße ergänzen und daher häufig zusammen verwendet werden - genau dort, wo es auf eine klare Unterscheidung ankommt. Dies führt wahrscheinlich zu Designproblemen für Programmierer, die dies lesen, wenn sie nicht angesprochen werden. (Und da das Codebeispiel, soweit ich das beurteilen kann, einfach keine Closures enthält, sondern nur eine Lambda-Funktion, hilft es nicht, zu erklären, warum Closures hilfreich sind.)
Nathan Tuggy
Nathan, dieses Codebeispiel ist eine Schließung in Swift. Sie können jemanden fragen, wenn Sie an mir zweifeln. Wenn es sich also um eine Schließung handelt, würden Sie dafür stimmen?
Bendrix,
1
Apple beschreibt in seiner Dokumentation ziemlich genau, was sie mit Abschluss meinen . "Closures sind eigenständige Funktionsblöcke, die weitergegeben und in Ihrem Code verwendet werden können. Closures in Swift ähneln Blöcken in C und Objective-C sowie Lambdas in anderen Programmiersprachen." Bei der Implementierung und Terminologie kann es verwirrend sein .