Closure

aus Wikipedia, der freien Enzyklopädie
Wechseln zu: Navigation, Suche
QS-Informatik

Dieser Artikel wurde wegen inhaltlicher Mängel auf der Qualitätssicherungsseite der Redaktion Informatik eingetragen. Dies geschieht, um die Qualität der Artikel aus dem Themengebiet Informatik auf ein akzeptables Niveau zu bringen. Hilf mit, die inhaltlichen Mängel dieses Artikels zu beseitigen, und beteilige dich an der Diskussion! (+)
Begründung: Siehe Diskussionsseite --Pjacobi 23:20, 16. Mär. 2009 (CET)

Eine Closure (oder Funktionsabschluss) ist eine anonyme Funktion, die Zugriffe auf ihren Erstellungskontext enthält. Beim Aufruf greift die Funktion dann auf diesen Erstellungskontext zu. Dieser Kontext (Speicherbereich, Zustand) ist außerhalb der Funktion nicht referenzierbar, d. h. nicht sichtbar.

Eine Closure beinhaltet zugleich Referenz auf die Funktion und den von ihr verwendeten Teil des Erstellungskontexts – die Funktion und die zugehörige Speicherstruktur sind in einer Referenz untrennbar abgeschlossen (closed term). Es ist vergleichbar mit einem Objekt mit „private“-Attributen und „public“-Methode(n): es enthält eine (implizite) Identität, einen Zustand und ein Verhalten.

In der Programmiersprachen-Syntax wird dies oft durch zwei verschachtelte Funktionen erreicht – die Hauptfunktion wird von einer weiteren Funktion eingeschlossen („abgeschlossen“). Diese „Abschluss“-Funktion enthält die benötigte Speicherstruktur (siehe Beispiele unten) und liefert die Referenz – die Closure.

Herkunft[Bearbeiten]

Closures sind ein Konzept, das aus den funktionalen Programmiersprachen stammt, zum ersten Mal in LISP auftrat und in seinem Dialekt Scheme erstmals vollständig unterstützt wurde. Daraufhin wurde es auch in den meisten späteren funktionalen Programmiersprachen (etwa Haskell, Ocaml) unterstützt.

Dynamische und lexikalische Closure[Bearbeiten]

Die erste Implementierung von Closures ergab sich relativ zwanglos aus der Art der Implementierung von Ausführungsumgebungen in Lisp. In den ersten Lisp-Implementierungen gab es keine lexikalische Skopierung. Die Ausführungsumgebung einer Anweisung bestand aus einer sogenannten A-Liste mit Variablenbindungen, die über eine einzelne Referenz erreichbar war. Eine Closure über einer Funktion bestand dann aus einem Paar, bestehend aus der Funktionsdefinition und der Referenz auf die zur Definitionszeit der Closure gültigen A-Liste. Dieses durch die Lisp-Funktion FUNCTION erzeugte Paar ist eine dynamische Closure mit der historischen Bezeichnung FUNARG (FUNctional ARGument). Gelangte das FUNARG später zur Ausführung, so geschah dies im Kontext der mitgebrachten, ge-"Closure"-ten A-Liste anstatt im Kontext der aktuell gültigen A-Liste.[1]

Die heute in Lisp wie in allen anderen Sprachen verwendete lexikalische Skopierung führt zur lexikalischen Closure, die auch in kompilierten Sprachen funktionsfähig ist. Sie entsteht erst durch aktives Eingreifen des Compilers, indem dieser die Bezüge der Funktion auf die innerhalb ihrerselbst freien und außerhalb von ihr gebunden Variablen identifiziert und Code erzeugt, der diese Bindungen mit der Funktion zusammen bei ihrer Rückgabe aus ihrem Definitionskontext zu einer Closure zusammensetzt. Dies geschieht, bevor diese Funktion – nun als Closure – dem Aufrufer zur Verfügung gestellt wird. Da diese Variablenbindung nun nicht mehr lexikalisch gebunden ist, kann sie nicht auf dem Stack verbleiben, sondern wird vom Laufzeitsystem auf den Heap gelegt. Bei gleichzeitiger Bildung mehrerer Closures über der gleichen Variablenbindung sorgt das Laufzeitsystem dafür, dass in beide Closures die gleiche Heap-basierte Kopie dieser Variablenbindung eingesetzt wird.

Vorteile und Eigenschaften[Bearbeiten]

Mit Closures können nicht sichtbare, aber kontrolliert veränderbare Bereiche erstellt werden, beispielsweise kann damit Datenkapselung realisiert oder Currying umgesetzt werden.

Eine Closure kann man als Objekt ansehen, das in der Regel nur eine Methode hat. Wird die Closure zusammen mit weiteren Closures über demselben Kontext erzeugt, so handelt es sich um ein Objekt mit mehreren Methoden. Die in der Closure eingeschlossenen Variablen aus dem erzeugenden Bereich können von der Closure als Attribute verwendet werden.

Die Erzeugung einer Closure ist mit deutlich weniger Arbeit verbunden als die Erstellung einer Klasse mit nur einer Methode. Wirklich nützlich sind Closures allerdings nur in der Funktionalen Programmierung, da sie hier immer dann verwendet werden können, wenn sie entweder selbst als Funktion aufgerufen oder als Parameter in einen Funktionsaufruf eingehen sollen. Im letzteren Fall kann sie als zur Laufzeit erzeugte Call-Back-Funktion agieren und ermöglicht so einem Anwendungsprogramm in erheblichen Maß während seiner Laufzeit den eigenen Kontrollfluss zu manipulieren. Dies wird allerdings meistens erst durch ein System von Closures praktisch sinnvoll ermöglicht. Auf dieser Tatsache beruht das didaktische Problem, unerfahrenen Programmierern die Anwendung von Closures nahezubringen.

Implementierungen[Bearbeiten]

Es existieren auch nicht-funktionale Programmiersprachen, die diese Funktion unterstützen. Hier wären Ada[2], Object Pascal (Delphi)[3][4], JavaScript[5], Smalltalk, Python, Lua, Ruby, C#, Visual Basic .NET, C++ (nur im Standard C++11), Groovy, Go, PHP, Perl und Swift zu nennen. Auch die objektorientierte Programmiersprache Java unterstützt eine limitierte Variante von Funktionsabschlüssen in Form anonymer und lokaler Klassen. Apple hat den gcc und Clang um Closures – genannt Block-Literals – für C erweitert und dies zur Standardisierung vorgeschlagen.[6]

Beispiele von Implementierungen[Bearbeiten]

Pseudocode[Bearbeiten]

Im folgenden Beispiel wird zunächst eine Funktion mutterfunktion definiert. Diese Funktion setzt eine lokale Variable namens kuchen und definiert eine lokale Funktion namens kindfunktion.

funktion mutterfunktion {
  setze kuchen = 'Apfelkuchen'
  funktion kindfunktion {
    gib_aus 'Ich esse #{kuchen}'
  }
  gib_zurück kindfunktion
}

Bei einem Aufruf gibt sie die lokale Funktion kindfunktion zurück.

Die globale Variable kind bekommt also die Funktion kindfunktion zugewiesen, sodass beim anschließenden Aufruf von kind folglich kindfunktion ausgeführt wird. Obwohl keine globale Variable kuchen existiert, gibt kindfunktion die Zeichenkette 'Ich esse Apfelkuchen' aus, weil sie auf ihren Erstellungskontext zugreifen kann, in dem die Variable kuchen mit 'Apfelkuchen' definiert ist. Wichtig zu beachten: Obwohl mutterfunktion schon einen Wert zurückgegeben hat – der Kontext also eigentlich nicht mehr existiert – kann kindfunktion darauf zugreifen – kindfunktion ist also eine Closure-Funktion.

setze kind = rufe_auf mutterfunktion
rufe_auf kind
Ich esse Apfelkuchen

Mit einer Änderung im Code wird nun der Wert der Variablen anzahl_kuchen in der mutterfunktion mit jedem Zugriff auf die Closure-Funktion um eins erhöht, womit sich ein Zähler realisieren lässt. Der Wert in anzahl_kuchen ist vor Manipulation geschützt und kann nur durch essen erhöht werden.

funktion mutterfunktion {
  setze anzahl_kuchen = 0
  funktion kindfunktion {
    setze  anzahl_kuchen = anzahl_kuchen + 1
    gib_aus 'Ich esse #{anzahl_kuchen} Kuchen'
  }
  gib_zurück kindfunktion
}

Durch Aufrufen kann auf einen sonst nicht sichtbaren Bereich zugegriffen werden und innerhalb der kindfunktion können Berechnungen mit sonst nicht veränderbaren Werten vorgenommen werden – das sind die wesentlichen Vorteile von Closures.

setze essen = rufe_auf mutterfunktion
rufe_auf essen
rufe_auf essen
rufe_auf essen
Ich esse 1 Kuchen
Ich esse 2 Kuchen
Ich esse 3 Kuchen

Common Lisp[Bearbeiten]

Dieses Beispiel verwendet eine Closure, um eine elegante Datenbankabfrage zu ermöglichen. Die Closure wird von der Funktion name-is geliefert. Durch die special function lambda wird eine namenlose Funktion erzeugt, innerhalb derer der Wert des Feldes name auf die Gleichheit mit einem String ngeprüft wird. Der Aufruf (name-is "Elke") liefert also eine Closure als Verbindung aus der anonymen Funktion und der Variablenbindung von n an den String "Elke". Diese kann einen Datensatz auf den Namensgleichheit mit "Elke" überprüfen. Die Closure kann direkt an die Funktion filter übergeben werden, die diese dann anwendet und das Ergebnis zurückgibt.

(defparameter *dbase* 
   '(("Elke"  "1.1.1980") ("Gabi"  "2.3.1981") ("Heidi" "4.5.1982") 
     ("Gabi"  "5.6.1983") ("Uschi" "7.8.1984")))
 
(defun name (l) 
   (car l))
 
(defun name-is (n)
  (lambda (l)
    (equal (name l) n)))
 
(defun filter (predicate list)
  (remove-if-not predicate list))

Diese Definitionen machen nun folgende elegante Abfrage möglich:

(print (filter (name-is "Gabi") *dbase*))

Sie ist folgendermaßen zu verstehen: Der Funktionsaufruf (name-is "Gabi") liefert eine Closure. Sie ist hier eine Verbindung aus dem Vergleichscode (equal (name l) n) aus der Funktion name-is und der Bindung des Strings "Gabi" an die Variable n. Damit handelt es sich semantisch um die Abfrage (equal (name l) "Gabi"). Dieser Vergleich wird als Closure an die Funktion filter übergeben, die diesen Vergleich anwendet. Ausführen dieser Filterung führt dann zu dem Ergebnis:

(("Gabi" "2.3.1981") ("Gabi" "5.6.1983"))

Perl[Bearbeiten]

Der Kontext eines beliebigen Code-Fragments wird unter anderem durch die zur Verfügung stehenden Symbole bestimmt:

 # pragma
 use strict;
 
 sub function {
   my ($var1, $var2) = @_; # Argumente in benannte Variablen kopieren
   # block code
     ...
 }

Im oben gezeigten Beispiel sind die Variablen $var1 und $var2 an jeder Stelle der Funktion gültig und sichtbar. Beim Verlassen der Funktion werden sie zusammen mit dem verlassenen Block aufgeräumt („gehen“ out of scope) und sind anschließend unbekannt. Jeder weitere Zugriff wäre ein Fehler.

Closures bieten nun die Möglichkeit, den Gültigkeitsbereich solcher Variablen über dessen offizielles Ende hinaus auszudehnen. Dazu wird im Scope einfach eine Funktion definiert, die die betreffenden Variablen verwendet:

 # pragma
 use strict;
 
 sub function {
   my ($var1, $var2) = @_;    
   return sub { print "Vars: $var1, $var2.\n" };
 }
 
 my $f = function("Hallo", 8);
 my $g = function("bar", "Y");
 
 # Aufruf von $f
 $f->();
 
 # Aufruf von $g
 $g->();

Das Laufzeitsystem stellt jetzt beim Verlassen der Funktion function fest, dass noch Referenzen auf die Blockvariablen $var1 und $var2 bestehen – der Rückgabewert ist eine anonyme Subroutine, die ihrerseits Verweise auf die Blockvariablen enthält. $var1 und $var2 bleiben deshalb mit ihren aktuellen Werten erhalten. Weil die Funktion auf diese Weise die Variablen konserviert, wird sie zur Closure.

Mit anderen Worten kann man auch nach dem Verlassen des eigentlichen Gültigkeitsbereichs der Variablen jederzeit den Aufruf $f->() und den Aufruf $g->() ausführen und wird im Ergebnis immer wieder die bei der Definition der Funktionen gültigen Werte der Variablen angezeigt bekommen.

Dies ergibt die Ausgabe:

Vars: Hallo, 8.
Vars: bar, Y.

Ändern kann man diese Werte nicht mehr, da die Variablen außerhalb der Closure nicht mehr verfügbar sind. Das liegt aber vor allem an der Funktionsdefinition: natürlich hätte die Closure die Werte nicht nur ausgeben, sondern auch bearbeiten oder auch aufrufendem Code wieder per Referenz zur Verfügung stellen können. In der folgenden Variante werden beispielsweise Funktionen zum Inkrementieren und Dekrementieren eingeführt:

 # pragma
 use strict;
 
 # function
 sub function
  {
   my ($var1, $var2) = @_;
 
   return (
       sub {print "Vars: $var1, $var2.\n"},
       sub {$var1++; $var2++;},
       sub {$var1--; $var2--;}
   );
 }
 
 # call the function
 my ($printer, $incrementor, $decrementor) = function(3,5);
 # use closures
 $printer->();
 $incrementor->();
 $printer->();
 $incrementor->();
 $incrementor->();
 $printer->();

Dies ergibt die Ausgabe:

Vars: 3, 5.
Vars: 4, 6.
Vars: 6, 8.

Closures lassen sich also beispielsweise dazu verwenden, den Zugriff auf sensible Daten zu kapseln.

Python[Bearbeiten]

Folgend ein einfaches Beispiel für einen Zähler in Python, der ohne einen (benannten) Container auskommt, der den aktuellen Zählerstand speichert.

 def closure():
    container = [0]
    def inc():
       container[0] += 1
    def get():
       return container[0]
    return inc, get

Im Beispiel werden innerhalb der closure-Funktion zwei Funktionsobjekte erstellt, die beide die Liste container aus ihrem jeweils übergeordneten Scope referenzieren. Ist die closure-Funktion also abgearbeitet (nach einem Aufruf) und werden die beiden zurückgegebenen Funktionsobjekte weiter referenziert, dann existiert die container-Liste weiter, obwohl der Closure-Scope bereits verlassen wurde. Auf diese Weise wird also die Liste in einem anonymen Scope konserviert. Man kann nicht direkt auf die Liste container zugreifen. Werden die beiden Funktionsobjekte inc und get nicht mehr referenziert, verschwindet auch der Container.

Die Closure im vorigen Beispiel wird dann auf die folgende Weise verwendet:

>>> i, g = closure()
>>> g()
0
>>> i()
>>> i()
>>> g()
2

OCaml[Bearbeiten]

OCaml erlaubt das in folgender Weise :

let counter, inc, reset =
  let n = ref 0 in
  (function () -> !n),    	    (* counter  *)
  (function () -> n:= !n + 1), (* incrementor *)
  (function () -> n:=0 )     	(* reset  *)

jetzt ist der Zähler wie folgt anwendbar :

# counter();; (* ergibt 0 *)
# inc();;
# counter();; (* ergibt 1 *)
# inc();inc();inc();;
# counter();; (* ergibt 4 *)
# reset();;
# counter();; (* ergibt 0 *)
# n;; (* n ist gekapselt *)
Unbound value n

Statt eines Integers können natürlich auf diese Weise beliebige Objekte oder Variablen beliebiger Typen gekapselt werden.

JavaScript[Bearbeiten]

In der Funktion f1 wird eine weitere Funktion f2 als Closure definiert;

var f1 = function () {      // eine äußere Funktion f1 definieren ...
    var wert = 22;          // ... und darin einen Namensraum erstellen.    
    var f2 = function () {  // eine innere Funktion definieren, ...
        return wert;        // ... die den Namensraum nach außen reicht.
    }
    return f2;   // f2 durch f1 zurückgeben, womit f2 zum closure wird.
}
var a = f1(); // a ist die von f1() zurückgegebene closure-Funktion, ...
document.write(f1() +"<br />"); // ... also: function() {return wert;}
document.write(typeof wert +"<br />"); // ist undefined
document.write(a()+"<br />"); // ergibt 22
document.write(f1()() +"<br />"); // ergibt 22, f2() ist hier aber nicht abrufbar

Obiges Beispiel etwas anders formuliert, die innere Funktion wird jetzt direkt aufgerufen:

var f3 = function () {
    var wert = 23;
    return function () {   // die Funktion f3 gibt gleich die closure-Funktion zurück!
        return wert;
    };
}
var b = f3(); // b ist wieder die von f3() zurückgegebene Funktion ...
document.write(b()+"<br />"); // ... und liefert jetzt als Ergebnis 23
document.write(b+"<br />"); // b bleibt aber weiterhin ein Funktionsaufruf!
document.write(f3()() +"<br />"); // liefert ebenfalls 23

Die eingebettete Funktion dient jeweils als Lieferant des in der übergeordneten Funktion definierten Wertes.

Die übergeordnete Funktion kann auch als anonyme Funktion definiert werden:

var wert = 24;
var c = (function() {  // die äußere als anonyme Funktion und ...
      return wert;   // ... darin die innere Funktion definieren.
    }());   // die Funktion jetzt noch mit (); aufrufen.
document.write(c+"<br />"); // ergibt 24

Die Closure kann auch mit einer Konstruktorfunktion erzeugt werden:

var d = (new Function("return wert;"))();   // mit einem Konstruktur definieren und aufrufen

Lua[Bearbeiten]

Lua hat eine eingebaute, und im Sinne der Programmierung auch intuitiv nutzbare Unterstützung für Closures, deren Implementierung ähnlich zu der in Python ist:

function adder(x)      -- Funktionserzeuger
    return function(y) -- anonyme, zu adder private Funktion
        return x+y     -- x stammt hier aus dem äußeren Kontext
    end
end

Eine Beispielnutzung sähe so aus:

add2 = adder(2)    -- hier wird die Closure erzeugt
print(add2(10))    --> Ausgabe 12
print(add2(-2))    --> Ausgabe 0

Eine Closure-Implementierung in Lua ist in[7] beschrieben.

Erlang[Bearbeiten]

Erlang als funktionale Sprache besitzt ebenfalls Closures, die allerdings Funs (Singular Fun, von function) genannt werden.

do_something(Fun) -> Fun(4).
 
main() ->
    Var = 37,
    F = fun(N) -> Var + N end.
    Result = do_something(F).
    % Result =:= 41 =:= 37 + 4

C#[Bearbeiten]

C# unterstützt Closures in Form von Delegates.[8]

private static Action CreateClosure()
{
    // Deklaration einer Variablen im lokalen Kontext
    var x = 0; 
 
    // Erstellung eines Closure-Delegate mit Hilfe eines Lambda-Ausrucks
    var closure = () => Console.WriteLine(x); 
 
    // Änderung am lokalen Kontext
    x = 1; 
 
    // Rückgabe der Closure in den übergeordneten Kontext 
    return closure;
}
 
static void Main()
{
    var closure = CreateClosure();
 
    // Im globalen Kontext
    // Variable x wird nur noch innerhalb der Closure referenziert
 
    // Führe Closure aus; Schreibt "1" auf die Konsole 
    closure(); 
}

Literatur[Bearbeiten]

  • Ralf H. Güting, Martin Erwig, Übersetzerbau, Springer (1999), ISBN 3-540-65389-9
  • Damian Conway, Object Oriented Perl
  • Oliver Lau, Andreas Linke, Torsten T. Will: Variablen to go – Closures in aktuellen Programmiersprachen. In: c’t 17/2013, S. 168ff. ISSN 0724-8679

Weblinks[Bearbeiten]

Einzelnachweise[Bearbeiten]

  1. John McCarthy u. a.: Lisp 1.5 Programmers Manual. www.softwarepreservation.org. Abgerufen am 12. März 2014.
  2. John Barnes: Rationale for Ada 2005
  3. Craig Stuntz: Understanding Anonymous Methods
  4. Barry Kelly: Tiburon: fun with generics and anonymous methods
  5. Closures in Javascript (englisch)
  6. N1370: Apple’s Extensions to C (PDF; 69 kB)
  7. The implementation of Lua 5.0
  8. Dustin Campbell: What's In A Closure. 9. Februar 2007, abgerufen am 12. April 2014 (englisch).