Откладывание кода при изменении области действия в Perl - PullRequest
5 голосов
/ 22 марта 2009

Я часто нахожу полезным иметь возможность планировать выполнение кода после выхода из текущей области. В моей предыдущей жизни в TCL друг создал функцию, которую мы назвали defer.

Включен код вроде: set fp [открыть "x"] defer ("close $ fp");

, который был вызван при выходе из текущей области. Основное преимущество заключается в том, что он всегда вызывается независимо от того, как / где я покидаю область действия.

Итак, я реализовал нечто подобное в Perl, но, похоже, есть более простой способ. Комментарии приветствуются.

То, как я это делал в Perl:

  • создать глобальную связанную переменную, которая содержит массив подпрограмм для выполнения.
  • всякий раз, когда я хочу запланировать вызов fn при выходе, я использую local для изменения массива. когда я покидаю текущую область видимости, Perl меняет глобальное на предыдущее значение потому что глобал привязан, я знаю, когда произойдет это изменение значения, и могу вызвать подпрограммы в списке.

Фактический код указан ниже.

Есть ли лучший способ сделать это? Кажется, это была бы необходимая возможность.

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

Ответы [ 5 ]

4 голосов
/ 22 марта 2009

Вместо того, чтобы использовать галстук для этого, я бы просто создал объект. Вы также можете избежать local таким же образом.

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

...
}

Когда переменная выходит из области видимости, у вас есть шанс что-то сделать в методе DESTROY.

Кроме того, в примере, который вы разместили, вы должны проверить, что сохраненные вами значения являются ссылками на код, и, вероятно, неплохо бы проверить, что значение VAL является ссылкой на массив:

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;
    }
4 голосов
/ 22 марта 2009

Ну, ваш конкретный случай уже обработан, если вы используете лексические файловые дескрипторы (в отличие от старых файловых дескрипторов голых слов). В других случаях вы всегда можете использовать метод DESTROY объекта, который гарантированно перейдет к нулевым ссылкам, когда он выходит из области видимости:

#!/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: мне больше нравится имя Брайана, Scope :: OnExit - гораздо более описательное имя.

3 голосов
/ 22 марта 2009

Вы можете попробовать B :: Крючки :: EndOfScope

Полагаю, это работает:

   use B::Hooks::EndOfScope; 

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

   foo();
1 голос
/ 22 марта 2009

Тривиально,

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

используется как:

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

(Дополнительный уровень наличия ссылки на ссылку на подпрограмму необходим только для обхода оптимизации (что, возможно, является ошибкой) при использовании анонимного подпункта без замыканий.)

1 голос
/ 22 марта 2009

Я думаю, что вы хотите что-то вроде Scope :: Guard , но это нельзя толкнуть. Хммм.

Спасибо.

...