Alle „coolen“ Programmiersprachen haben sogenannte Closures. Java ist nicht cool, deshalb braucht man das dort nicht… 😉 Aber bitte bis zum Schluss weiterlesen…
Zunächst gibt es um diesen Begriff eine gewisse Verwirrung. Gemeint werden damit oft anonyme Funktionen, die man in einem Programm herumreichen kann, wie andere Werte und Objekte. Das ist so etwas, was man von funktionalen Programmiersprachen erwarten kann, auch wenn es noch nicht ausreicht, um eine Programmiersprache funktional zu machen. Die Funktionspointer in C sind etwas weniger als das, weil sie ja eine statisch definierte Funktion beschreiben, die erst im Kontext der Funktion, der sie als Parameter oder Variable sichtbar gemacht werden, anonym wirken. Andere Programmiersprachen wie Ruby, Perl, JavaScript, die meisten Lisp-Dialekte einschließlich Clojure, Scala, und viele andere mehr, in neueren Versionen wohl sogar C# und Java, haben solche anonymen Funktionen.
Zur Closure werden sie aber erst durch die Einbindung von Variablen aus dem Kontext, in dem sie definiert werden.
Hier wird etwas darüber geschrieben, wie Closures in Ruby funktionieren:
Closures in Ruby
Doch nun zu Perl. In diesem Zusammenhang muss man darauf achten, ob die lokalen Variablen mit „my“ oder mit „local“ deklariert.
In der Regel ist „my“ das, was man verwenden sollte und in der Regel macht es keinen Unterschied. Aber in diesem Fall sieht man es: Variablen, die mit my deklariert wurden, werden im definierenden Kontext der anonymen Funktion eingebunden, solche, die mit local deklariert wurden, im aufrufenden Kontext. Es gibt auch noch „our“, wobei der Unterschied zu „my“ in diesem Blogbeitrag erklärt ist. In diesem Beispiel verhält sich „our“ genauso wie „my“.
Hier ein Beispiel für die Verwendung von „my“:
#!/usr/bin/perl
# 2nd-order function, returns a reference to a function
sub createAdder($) {
# local variable, will be accessed by anonymous function (early binding)
my $x = $_[0];
# define anonymous function, obtain reference to it and assign it to result
my $result = ( sub {
# @_ refers to parameters of anonymous function
my $y = $_[0];
# $x comes from createAdder, $y comes from context of caller and $z is parameter of function
return ($x, $y, $x+$y);
});
return $result;
}
# local variable, will not be accessed by called anonymous function
my $x = 100;
my $px = 30;
# obtain reference to function
my $ff = createAdder($px);
# call function
for (my $i = 0 ; $i < 3; $i++) {
my @arr = $ff->($i);
my $arr_str = "( " . join(", ", @arr) . " )";
print "x=$x i=$i px=30 (px, y, px+i)=", $arr_str , "\n";
}
print "\n";
$x = 1000;
$px = 60;
my $ff = createAdder(60);
# call function
for (my $i = 0 ; $i < 3; $i++) {
my @arr = $ff->($i);
my $arr_str = "( " . join(", ", @arr) . " )";
print "x=$x i=$i px=60 (px, i, px+i)=", $arr_str , "\n";
}
Man sieht also, dass das $x aus createAdder() in die anonyme Funktion eingebaut wird und es kommt diese Ausgabe:
x=100 i=0 px=30 (px, y, px+i)=( 30, 0, 30 )
x=100 i=1 px=30 (px, y, px+i)=( 30, 1, 31 )
x=100 i=2 px=30 (px, y, px+i)=( 30, 2, 32 )
x=1000 i=0 px=60 (px, i, px+i)=( 60, 0, 60 )
x=1000 i=1 px=60 (px, i, px+i)=( 60, 1, 61 )
x=1000 i=2 px=60 (px, i, px+i)=( 60, 2, 62 )
Mit local sieht es so aus:
#!/usr/bin/perl
# 2nd-order function, returns a reference to a function
sub createAdder($) {
# local variable, will be ignored by anonymous function (late binding)
local $x = $_[1];
# define anonymous function, obtain reference to it and assign it to result
my $result = ( sub {
# @_ refers to parameters of anonymous function
my $y = $_[0];
# $x comes from createAdder, $y comes from context of caller and $z is parameter of function
return ($x, $y, $x+$y);
});
return $result;
}
# local variable, will be accessed as $y by anonymous function
local $x = 100;
local $px = 30;
# obtain reference to function
my $ff = createAdder($px);
# call function
for (my $i = 0 ; $i < 3; $i++) {
my @arr = $ff->($i);
my $arr_str = "( " . join(", ", @arr) . " )";
print "x=$x i=$i px=30 (px, y, px+i)=", $arr_str , "\n";
}
print "\n";
$x = 1000;
$px = 60;
$ff = createAdder(60);
# call function
for (my $i = 0 ; $i < 3; $i++) {
my @arr = $ff->($i);
my $arr_str = "( " . join(", ", @arr) . " )";
print "x=$x i=$i px=60 (px, i, px+i)=", $arr_str , "\n";
}
Man sieht also, dass diesmal das $x aus dem aufrufenden Kontext in der anonyme Funktion verwendet wird und es kommt diese Ausgabe:
x=100 i=0 px=30 (px, y, px+i)=( 100, 0, 100 )
x=100 i=1 px=30 (px, y, px+i)=( 100, 1, 101 )
x=100 i=2 px=30 (px, y, px+i)=( 100, 2, 102 )
x=1000 i=0 px=60 (px, i, px+i)=( 1000, 0, 1000 )
x=1000 i=1 px=60 (px, i, px+i)=( 1000, 1, 1001 )
x=1000 i=2 px=60 (px, i, px+i)=( 1000, 2, 1002 )
Diese Programme haben jeweils eine Funktion 2. Ordnung, createAdder(), die selbst eine Funktion zurückgibt.
Nun stellt sich aber heraus, dass Java ein sehr ähnliches, natürlich einmal wieder etwas umständlicheres Konstrukt hat. Das sind anonyme innere Klassen, die ein Interface mit genau einer Methode implementieren. Und Java 8 hat die Closures jetzt auch ohne diesen Umweg. Aber das ist sicher einmal ein anderer Blog-Beitrag.