Pregunta

A menudo resulta útil poder programar el código para ser ejecutado al salir del ámbito actual. En mi vida anterior en TCL, un amigo crea una función que llamamos Defer.

Se habilita un código como:     conjunto fp [abierta "x"]     aplazar ( "close $ fp");

que se invoca cuando el ámbito actual se cierra. El principal beneficio es que siempre ha invocado no importa cómo / dónde dejo alcance.

Así que he implementado algo similar en Perl, pero parece que habría una manera más fácil. Comentarios a las críticas son bienvenidas.

La forma en que lo hice en Perl:

  • crear una variable global, atado, que contiene una matriz de submarinos a ser ejecutado.
  • cada vez que quiero programar una fn que se invoca en la salida, lo uso local para cambiar la matriz. cuando salga del ámbito actual, Perl cambia el nivel global hasta el valor anterior porque está ligado al mundial, sé cuando esto sucede el cambio de valor y puede invocar a los submarinos en la lista.

El código real está por debajo.

¿Hay una mejor manera de hacer esto? Parece que esto sería una capacidad comúnmente necesario.

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";
¿Fue útil?

Solución

Bueno, el caso específico ya se maneja si utiliza controladores de archivo léxicas (a diferencia de los antiguos controladores de archivo bareword estilo). Para otros casos, se puede utilizar siempre el método de un objeto garantizada para ir a cero referencias destruye cuando se sale del ámbito:

#!/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: me gusta el nombre de Brian mejor, Alcance :: AlSalir es un nombre mucho más descriptivo

.

Otros consejos

En lugar de utilizar el lazo de esto, creo que acababa de crear un objeto. También puede evitar la local de esa manera también.

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

...
}

Cuando la variable se sale del ámbito, usted tiene la oportunidad de hacer las cosas en el método destroy.

Además, en el ejemplo que has publicado, es necesario comprobar que los valores se almacenan son las referencias de código, y es probablemente una buena idea para comprobar que el valor VAL es una referencia a un array:

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;
    }

Es posible que desee probar B :: Ganchos :: EndOfScope

Creo que esto funciona:

   use B::Hooks::EndOfScope; 

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

   foo();

Creo que desee algo así como Ámbito :: Guardia, pero puede' t ser empujada. Hmmm.

Gracias.

Trivialmente,

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

utilizado como:

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

(El nivel adicional de tener una referencia a una referencia a un sub es necesario sólo para evitar una optimización (que es sin duda un error) cuando se utiliza un sub anónima no cierre.)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top