2009-03-21 10 views
5

Ich finde es oft nützlich, in der Lage zu sein, Code zu programmieren, der beim Verlassen des aktuellen Bereichs ausgeführt werden soll. In meinem vorherigen Leben in TCL hat ein Freund eine Funktion erstellt, die wir Defer nennen.Code bei Bereichsänderung in Perl verschieben

Es aktiviert Code wie: set fp [open "x"] verzögern ("close $ fp");

wurde aufgerufen, als der aktuelle Bereich beendet wurde. Der Hauptvorteil ist, dass es immer aufgerufen wird, egal wie/wo ich den Umfang verlasse.

Also habe ich etwas ähnliches in Perl implementiert, aber es scheint, dass es einen einfacheren Weg geben würde. Kommentare Kritik willkommen.

Die Art und Weise habe ich es in Perl:

  • schaffen eine globale, gebunden Variable, die eine Reihe von U-Boote hält ausgeführt werden.
  • Immer wenn ich einen Fn planen möchte, der beim Beenden aufgerufen wird, verwende ich local, um das Array zu ändern. Wenn ich den aktuellen Bereich belasse, ändert Perl den globalen Wert auf , da das globale gebunden ist, weiß ich, wann diese Wertänderung passiert und kann die Subs in der Liste aufrufen.

Der eigentliche Code ist unten.

Gibt es einen besseren Weg, dies zu tun? Scheint, dies wäre eine häufig benötigte Fähigkeit.

use strict; 

package tiescalar; 

sub TIESCALAR { 
    my $class = shift; 

    my $self = {}; 
    bless $self, $class; 
    return $self; 
} 

sub FETCH { 
    my $self = shift; 
    return $self->{VAL}; 
} 

sub STORE { 
    my $self = shift; 
    my $value = shift; 

    if (defined($self->{VAL}) && defined($value)) { 
    foreach my $s (@{$self->{VAL}}) { &$s; } 
    } 
    $self->{VAL} = $value; 
} 

1; 

package main; 

our $h; 
tie($h, 'tiescalar'); 
$h = []; 
printf "1\n"; 
printf "2\n"; 

sub main { 
    printf "3\n"; 
    local $h = [sub{printf "9\n"}]; 
    push(@$h, sub {printf "10\n";}); 
    printf "4\n"; 
    { 
    local $h = [sub {printf "8\n"; }]; 
    mysub(); 
    printf "7\n"; 
    return; 
    } 
} 

sub mysub { 
    local $h = [sub {printf "6\n"; }]; 
    print "5\n"; 
} 

main(); 

printf "11\n"; 

Antwort

4

Well , Ihr spezifischer Fall wird bereits behandelt, wenn Sie lexikalische Dateihandles verwenden (im Gegensatz zu den alten Barwort-Dateihandles). In anderen Fällen können Sie immer die Methode eines Objekts DESTROY verwenden, um Null Referenzen gehen gewährleistet, wenn es aus dem Geltungsbereich:

#!/usr/bin/perl 

use strict; 
use warnings; 

for my $i (1 .. 5) { 
    my $defer = Defer::Sub->new(sub { print "end\n" }); 
    print "start\n$i\n"; 
} 

package Defer::Sub; 

use Carp; 

sub new { 
    my $class = shift; 
    croak "$class requires a function to call\n" unless @_; 
    my $self = { 
     func => shift, 
    }; 
    return bless $self, $class; 
} 

sub DESTROY { 
    my $self = shift; 
    $self->{func}(); 
} 

ETA: Ich mag Brians Namen besser, Scope :: OnExit ist eine viel beschreibender Name.

1

Ich glaube, Sie so etwas wie Scope::Guard wollen, aber es kann nicht verschoben werden. Hmmm.

Danke.

4

Anstatt Krawatte dafür zu verwenden, denke ich, ich würde nur ein Objekt erstellen. Sie können auch die local auf diese Weise vermeiden.

{ 
my $defer = Scope::OnExit->new(@subs); 
$defer->push($other_sub); # and pop, shift, etc 

... 
} 

Wenn die Variable den Gültigkeitsbereich verlässt, haben Sie eine Chance, die Dinge in der DESTROY-Methode zu tun.

Auch in dem Beispiel, das Sie geschrieben, müssen Sie überprüfen, dass die Werte, die Sie speichern sind Codereferenzen, und es ist wahrscheinlich eine gute Idee zu überprüfen, ob der VAL-Wert ist ein Array-Referenz:

 
sub TIESCALAR { bless { VAL => [] }, $_[0] } 

sub STORE { 
    my($self, $value) = @_; 

    carp "Can only store array references!" unless ref $value eq ref []; 

    foreach { @$value } { 
     carp "There should only be code refs in the array" 
      unless ref $_ eq ref sub {} 
     } 

    foreach (@{ $self->{VAL}}) { $_->() } 


    $self->{VAL} = $value; 
    } 
3

Möglicherweise möchten Sie ausprobieren B::Hooks::EndOfScope

Ich glaube, das funktioniert:

use B::Hooks::EndOfScope; 

    sub foo { 
     on_scope_end { 
       $codehere; 
     }; 
     $morecode 
     return 1; # scope end code executes. 
    } 

    foo(); 
+0

Es ist eine gute Idee, aber ich beabsichtige, eine bessere Version zu machen Das. Die Implementierung ist ein bisschen umständlich IMHO. –

1

Trivialerweise

sub OnLeavingScope::DESTROY { ${$_[0]}->() } 

verwendet wie:

{ 
    ... 
    my $onleavingscope = bless \sub { ... }, 'OnLeavingScope'; 
    my $onleavingscope2 = bless \\&whatever, 'OnLeavingScope'; 
    ... 
} 

(Die zusätzliche Ebene von Hav Ein Verweis auf einen Verweis auf ein Sub ist nur notwendig, um eine Optimierung zu umgehen (das ist wohl ein Bug), wenn eine nicht geschlossene anonyme Sub.