Отсрочка кода при изменении области действия в Perl

StackOverflow https://stackoverflow.com/questions/670179

  •  21-08-2019
  •  | 
  •  

Вопрос

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

Это включало код, подобный:установить fp [открыть "x"] отложить ("закрыть $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";
Это было полезно?

Решение

Что ж, ваш конкретный случай уже обработан, если вы используете лексические дескрипторы файлов (в отличие от дескрипторов файлов без слов старого стиля).В других случаях вы всегда можете использовать метод 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 - гораздо более описательное имя.

Другие советы

Вместо того чтобы использовать tie для этого, я думаю, я бы просто создал объект.Вы также можете избежать 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;
    }

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

Я верю, что это работает:

   use B::Hooks::EndOfScope; 

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

   foo();

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

Спасибо.

Тривиально,

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top