Closures in Perl

angelsfall

angelsfall

Routinier
Hi.

Ich hab mir nun alles über Closures in Perl angelesen. Ich weiss, was sie sind, wie ich sie programmiere, welchen Sinn sie haben.

Aber wann setzt man diese Teile ein?

Sprich: Standardfälle bzw. Real-Life-Examples, nicht solche Pseudobeispiele, wie es sie überall gibt. Bisher hab ich die nämlich nicht gebraucht und irgendwie konnte ich bisher alles ohne sie lösen. Und man soll sie ja nicht zwingend verwenden. Es gibt zwar einige bessere Beispiele, aber wirklich klar ist mir die Benutzung noch nicht ...

Any suggestions? :)

Quellen:
Programming Perl, O'Reilly
Objektorientiert Programmieren mit Perl, Addison-Wesley
http://www.stonehenge.com/merlyn/LinuxMag/col09.html
http://www.perlmonks.org/?node_id=75792
 
Hm, noch keiner seinen Senf dazugegeben, das Thema ist doch recht interessant ...:D

Ich benutze die recht häufig, meistens beim programmieren von CGI-Programmen unter Apache (mod_perl). Hauptgrund ist für mich die bessere Übersichtlichkeit. Wenn ich z.B. in einer Schleife was aus der Datenbank lese und unterschiedliche Ausgaben für verschiedene Nutzer der Seite mache, dann schaut das z.B. wie folgt aus:
Code:
...
    my $display_user = sub {
        my ($user_id, $username) = @_;
        return $username;
    };

    if ($param->isADMIN()) {
        my $orig = $display_user;
        $display_user = sub {
            # Admin gets link to user profile ...
            return $cgi->url({-href=>'/cgi-bin/foo.pl?user_id=' . $user_id}, &$orig(@_));
        };
    }

    if ($param->isGUEST()) {
        $display_user = sub {
            return ''; # hide username output for guests ...
        };
    }

    ...
    my $ret = '';
    while (($user_id, $username) = $db->fetchrow_array()) {
        $ret .= &$display_user($user_id, $username);
    }

    ...

(Mal so eben aus dem Kopf hinprogrammiert, muß nicht fehlerfrei sein).

Ich erinnere mich auch mal an ein Programm wo ich unterschiedliche Berechnungsmethoden mit zwei Arrays brauchte, das drumherum war gleich, nur die Berechnung war unterschiedlich:
Code:
...
    my $calc = undef;

    if ($arg == 1) {
        $calc = sub { my ($a, $b) = @_; return $a - $b; };
    }
    if ($arg == 2) {
        $calc = sub { my ($a, $b) = @_; return $b - $a; };
    }
    if ($arg == 3) {
        $calc = sub { my ($a, $b) = @_; return $a * $b; };
    }
    if (!defined($calc)) {
        $calc = sub { my ($a, $b) = @_; return $a + $b; };
    }

    my @arr1 = (....);
    my @arr2 = (....);

    my @result = berechnen(\@arr1, \@arr2, $calc);

    ....

Sicher kann man das anders lösen, ist halt Geschmackssache. Wenn man sich erstmal dran gewöht hat, mag man sie nicht mehr missen.

Heiko
 
Zuletzt bearbeitet:
So mein Senf auch noch dazu: ;)

Neben dynamischen Funktionsnamen kann man closures auch für den Fall verwenden, wenn man Funktionen innerhalb von Funktionen definieren will, die Ihre Variablen teilen:

Code:
#!/usr/bin/perl -w                                                                                       

use strict;

print fkt1a('bi')."\n";  # erzeugt Warnung                                                               
print fkt2a('bi')."\n";  # erzeugt keine Warnung                                                         

sub fkt1a {
   my $x = $_[0];
   sub fkt1b { return $x.'ba' }
   return fkt1b().'bo';
}

sub fkt2a {
   my $x = $_[0];
   local *t = sub { return $x.'ba' };
   return t().'bo';
}

Gruss, Xanti
 
Zuletzt bearbeitet:
Danke für eure Antworten.

@hwj: Wenn ich das bei dir richtig interpretiert habe, benutzt du Verweise auf Subroutinen, aber keine Closures. Der Vorteil von Verweisen auf Subroutinen ist es, dass sie austauschbar sind wie man es ja besonders an deinem letzten Beispiel gut sieht. Closures sind von da ja gar nicht so weit entfernt. Zu einer Closures würde noch die Variable fehlen, die sich in der Subroutine befindet, die wiederum die Subroutine beinhaltet, die zurückgegeben wird, bzw der Verweis darauf. Also eine Closure ist praktisch eine Subroutine, an die Daten gebunden sind. Die Closure ist wiederum an eine andere Subroutine gebunden.

@Xanti: Was genau macht das Sternchen vor dem Variablennamen in deinem zweiten Beispiel? Und wieso benutzt du "local" und nicht "my"? Soweit ich weiss, speichert "local" den Wert der Variable, den sie hat, temporär zwischen, um in diesem "scope" solange einen anderen Wert anzunehmen und ihn später wieder zurückzuspeichern, während "my" eine ganz neue Variable im Stack anlegt. Aber was das an dieser Stelle macht, ist mir noch nicht ganz klar ...
Müsste man in deinem ersten Beispiel nicht einfach nur eine Referenz auf die Routine und nicht die Routine selbst übergeben, um die Warnung zu vermeiden (so wie in deinem zweiten Beispiel, wobei mir das ja noch nicht ganz klar ist ;)) bzw. einfach eine anonyme Subroutine?
 
Mit *t wird eine Paketvariable vereinbart. Um diese auf den Bereich lokal zu beschränken, benutzt man local. Eigentlich ist das Ganze in seiner Funktion eine anonyme Subroutine. Ein bisschen verwirrt bin ich nun doch: gibts eigentlich einen Unterschied zwischen anonymen Subroutinen und Closures?

Gut, mein Beispiel zeigt nun nicht unbedingt, wofür man closures einsetzt, eher wofür man sie "zweckentfremden" kann. Eine Bindung von Funktion und Daten existiert nicht direkt.

Daher ein besseres Beispiel: man kann Objekte in Perl erzeugen, ohne objektorientiert programmieren zu können. Bei Bedarf liefer ich ein Beispiel.

Gruss, Xanti
 
Zuletzt bearbeitet:
Es gibt definitiv einen Unterschied zwischen anonymen Subroutinen und Closures. Es geht eben um diese eine Variable, die konserviert wird. Eine Closure muss nicht zwingen durch eine anonyme Subroutine erzeugt werden. Sie kann auch einen Namen tragen, wird aber eben nur als Referenz übergeben.

Beispiel mit Referenz auf Subroutine:
Code:
#!/usr/bin/perl -wl

use strict;

sub output {
	my @data = @_;
	my $string = join " " , @data;
	return $string;
}

my $makeout = \&output;
print $makeout->(qw/ich bin eine kleine kuh/);

# Gleiches Beispiel mit Referenz auf anonyme Subroutine
my $makeout2 = sub {
	my @data = @_;
	my $string = join " " , @data;
	return $string;
};

print $makeout2->(qw/du bist ein kleiner hase/) ;

Komplexeres Beispiel mit anonymen Referenzen
(In Anlehnung an das Beispiel von R. Schwartz)
Code:
#!/usr/bin/perl -w

use strict;

sub queryData {
	my %args = @_;
	my $input = $args{'input'};
	my $output = $args{'output'};
	my $process = $args{'process'};
	my $prompt = $args{'prompt'};

	$output->($prompt);
	while (my $cmd = $input->()) {
		$output->($process->($cmd));
		$output->($prompt);
	}
}

my $makeShell = \&queryData;
$makeShell->(
	'input'   => sub { return <STDIN> },
	'output'  => sub { print @_ },
	'process' => sub {
		my $cmd = $_[0];
		chomp($cmd);
		last if ($cmd eq "exit");
		return qx/$cmd/;
	},
	'prompt'  => "myShell> ",
);

Beispiel mit Closures:
Code:
#!/usr/bin/perl -wl

use strict;

sub makeSetGet {
	my $a; # Diese Variable macht die unten zurückgegebenen Subroutinen zur Closures.

	return {
		'set' => sub { $a = shift },
		'get' => sub { $a }
	}
}

my $a = makeSetGet();

$a->{set}->("kuh");
print $a->{'get'}->();

$a->{set}->("hase");
print $a->{'get'}->();
 
Die Variable macht also den grossen Unterschied. Wieder was gelernt. Ist wirklich eine interessante Diskussion. :)

Theoretisch solltest Du Dir mittlerweile Deine Ausgangsfrage selber beantworten können. Mit Closures kann man Funktionen (Methoden) an Variablen binden und damit Objekte erzeugen.

Gruss, Xanti
 
Theoretisch solltest Du Dir mittlerweile Deine Ausgangsfrage selber beantworten können.

Ja. Theoretisch kann ich mir das auch alles Beantworten. Wie gesagt, mir fehlt die Praxis. Wann sind sie nützlich und gibt es Standardfälle? Weil das ist der Punkt, der mir noch zu einem glücklichen Zusammensein mit Closures fehlt ;)

Aber vielleicht kommt das auch einfach mit der Zeit ...
 
Ich denke mal, ausserhalb der objektorientierten Programmierung gibt wenig Anwendung für Closures.
 
Ich denke mal, ausserhalb der objektorientierten Programmierung gibt wenig Anwendung für Closures.

Komisch, und ich dachte immer, dass sie grade bei OOP überflüssig seien ;)
Wenn man sich so umhört, werden Closures glaube ich eher selten benutzt, aber trotzdem sehr angepriesen. Was ich aber momentan more sexy finde, sind die Referenzen auf anonyme Subroutinen. Damit lassen sich schöne Dinge machen ;)

(Anmerkung: Habe irgendwo gelesen, dass Closures in der Praxis meist zum Iterieren über Arrays benutzt werden und teilweise auch, um Rekursion zu vermeiden. Belegen kann ich die Aussagen jetzt allerdings nicht. Muss ma schaun, ob ich da noch was finde.)
 
Eine Closure muss nicht zwingen durch eine anonyme Subroutine erzeugt werden. Sie kann auch einen Namen tragen, wird aber eben nur als Referenz übergeben.
Ich muss mich hier etwas korrigieren.
Eine Closure sollte eine anonyme Subroutine sein, da das Problem mit benamten Subroutinen, die in einer Subroutine definiert sind (=nested subroutines), ist, dass diese eine Kopie dieser "konservierten" Variable, also der Umgebung der äußeren Subroutine, des ersten Aufrufs der äußeren Subroutine behalten und diese bei erneuten Aufrufen nicht mitaktualisert wird. Dieses Problem wird durch anonyme Subreferenzen behoben.

Funktioniert nicht:
Code:
#!/usr/bin/perl

  use strict;

  sub print_power_of_2 {
    my $x = shift;

    sub power_of_2 {
      return $x ** 2; 
    }

    my $result = power_of_2();
    print "$x^2 = $result\n";
  }

  print_power_of_2(5);
  print_power_of_2(6);
Output:
Code:
  5^2 = 25
  6^2 = 25

Funktioniert:
Code:
  #!/usr/bin/perl

  use strict;

  sub print_power_of_2 {
    my $x = shift;

    # hier wurde die routine "anonymisiert"
    my $func_ref = sub {
      return $x ** 2;
    };

    my $result = &$func_ref();
    print "$x^2 = $result\n";
  }

  print_power_of_2(5);
  print_power_of_2(6);
Output:
Code:
  5^2 = 25
  6^2 = 36

Quelle: http://www.perl.com/pub/a/2002/05/07/mod_perl.html
 
Im Buch "Programmieren mit Perl" steht noch folgendes Beispiel:
Code:
print "Sei ", red("vorsichtig"), "mit diesem ", green("Licht"), "!!!";
Soll eine HTML ausgabe sein mit verschiedenen farbigen Wörter.

Code:
@colors = qw(red blue green yellow orange purple violet);
for my $name (@colors) {
     no strict 'refs';     # Symbolische Referenzen erlauben.
     *$name = *{uc $name} = sub { "<FONT COLOR='$name'>@_</FONT>" };
}

Ich kann nicht so gut Perl programmieren und habe das Buch einfach einmal gekauft, aus diesem Grund gibt es keine Erklärung...

Aber ich denke es zeigt ein mögliches Anwendungsszenario.
 
Streng genommen handelt es sich dabei aber um anonyme Subroutinen, nicht um Closures (s.o.). :)
 
Streng genommen handelt es sich dabei aber um anonyme Subroutinen, nicht um Closures (s.o.). :)
Ja, richtig. Wobei ich sehr oft lese, dass anonyme Subroutinen als Closures bezeichnet werden. Aber der Name würde da auch keinen Sinn machen (closure - das Verschließen), eine Variable wird eben eingeschloßen (und somit konserviert und am Leben erhalten). Trotzdem ein schönes Beispiel für anonyme Subroutinen ;)
 
Zurück
Oben