Оператор триггера Perl прослушивается? Он имеет глобальное состояние, как я могу сбросить его? - PullRequest
29 голосов
/ 27 января 2010

Я встревожен. ОК, так что это была, вероятно, самая забавная Perl ошибка, которую я когда-либо обнаруживал. Даже сегодня я изучаю новые вещи о Perl. По сути, оператор триггера .., который возвращает false до тех пор, пока левая сторона не вернет true , а затем true до тех пор, пока правая сторона возвращает false сохранить глобальное состояние (или это то, что я предполагаю.)

Можно ли его сбросить (возможно, это было бы хорошим дополнением к Perl 4-esque, который вряд ли когда-либо использовался reset())? Или нет способа безопасно использовать этот оператор?

Я также не вижу, чтобы это (бит глобального контекста) было задокументировано где-либо в perldoc perlop Это ошибка?

код

use feature ':5.10';
use strict;
use warnings;

sub search {
    my $arr = shift;
    grep { !( /start/ .. /never_exist/ ) } @$arr;
}

my @foo = qw/foo bar start baz end quz quz/;
my @bar = qw/foo bar start baz end quz quz/;

say 'first shot - foo';
say for search \@foo;

say 'second shot - bar';
say for search \@bar;

Спойлер

$ perl test.pl
first shot
foo
bar
second shot

Ответы [ 6 ]

34 голосов
/ 27 января 2010

Может кто-нибудь уточнить, в чем проблема с документацией? Это четко указывает:

Each ".." operator maintains its own boolean state.

Существует некоторая неопределенность в том, что означает "каждый", но я не думаю, что документация будет хорошо подкреплена сложным объяснением.

Обратите внимание, что другие итераторы Perl (each или скалярный контекст glob) могут привести к тем же проблемам. Поскольку состояние для each связано с конкретным хешем, а не с конкретным битом кода, each можно сбросить, вызвав (даже в пустом контексте) keys для хеша. Но для glob или .. нет никакого механизма сброса, кроме как вызвать итератор, пока он не будет сброшен. Пример ошибки:

sub globme {
    print "globbing $_[0]:\n";
    print "got: ".glob("{$_[0]}")."\n" for 1..2;
}
globme("a,b,c");
globme("d,e,f");
__END__
globbing a,b,c:
got: a
got: b
globbing d,e,f:
got: c
Use of uninitialized value in concatenation (.) or string at - line 3.
got: 

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

Отдельные закрытия:

sub make_closure {
    my $x;
    return sub {
        $x if 0;  # Look, ma, I'm a closure
        scalar( $^O..!$^O ); # handy values of true..false that don't trigger ..'s implicit comparison to $.
    }
}
print make_closure()->(), make_closure()->();
__END__
11

Закомментируйте строку $x if 0, чтобы увидеть, что неотключения имеют одну операцию .., общую для всех «копий», с выводом 12.

Тема:

use threads;
sub coderef { sub { scalar( $^O..!$^O ) } }
coderef()->();
print threads->create( coderef() )->join(), threads->create( coderef() )->join();
__END__
22

Потоковый код начинается с того, что состояние .. было до создания потока, но изменения его состояния в потоке изолированы от воздействия на что-либо еще.

Рекурсия:

sub flopme {
    my $recurse = $_[0];
    flopme($recurse-1) if $recurse;
    print " "x$recurse, scalar( $^O..!$^O ), "\n";
    flopme($recurse-1) if $recurse;
}
flopme(2)
__END__
1
 1
2
  1
3
 2
4

Каждая глубина рекурсии является отдельным .. оператором.

18 голосов
/ 27 января 2010

Трюк не использует тот же триггер , поэтому вам не о чем беспокоиться. Просто сделайте функцию генератора, чтобы дать вам новую подпрограмму с новым триггером, который вы используете только один раз:

sub make_search {
    my( $left, $right ) = @_;
    sub {
        grep { !( /\Q$left\E/ .. /\Q$right\E/ ) } @{$_[0]};
        }
}

my $search_sub1 = make_search( 'start', 'never_existed' );
my $search_sub2 = make_search( 'start', 'never_existed' );


my @foo = qw/foo bar start baz end quz quz/;

my $count1 = $search_sub1->( \@foo );
my $count2 = $search_sub2->( \@foo );

print "count1 $count1 and count2 $count2\n";

Я тоже пишу об этом в Сделайте эксклюзивные операторы триггеров .

7 голосов
/ 27 января 2010

Обходной путь / хак / чит для вашего конкретного случая - добавить конечное значение в ваш массив:

sub search { 
  my $arr = shift;
  grep { !( /start/ .. /never_exist/ ) } @$arr, 'never_exist';
} 

Это гарантирует, что RHS оператора дальности в конечном итоге будет истинным.

Конечно, это никоим образом не является общим решением.

На мой взгляд, это поведение не четко задокументировано. Если вы можете составить четкое объяснение, вы можете применить патч к perlop.pod через perlbug.

7 голосов
/ 27 января 2010

«Оператор диапазона» .. задокументирован в perlop в разделе «Операторы диапазона». Просматривая документацию, кажется, что нет никакого способа сбросить состояние оператора ... Каждый экземпляр оператора .. сохраняет свое собственное состояние , что означает, что нет никакого способа сослаться на состояние какого-либо конкретного оператора ...

Похоже, он разработан для очень маленьких скриптов, таких как:

if (101 .. 200) { print; }

В документации указано, что это сокращение от

if ($. == 101 .. $. == 200) { print; }

Каким-то образом использование $. неявно там (инструментальное средство указывает на комментарий, который тоже задокументирован). Идея состоит в том, что этот цикл запускается один раз (до $. == 200) в данном экземпляре интерпретатора Perl, и поэтому вам не нужно беспокоиться о сбросе состояния .. flip -flop.

Этот оператор не слишком полезен в более общем контексте многократного использования по указанным вами причинам.

2 голосов
/ 27 января 2010

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

1 голос
/ 27 января 2010

Каждое использование оператора .. поддерживает свое собственное состояние. Как сказал Алекс Браун, вам нужно оставить это в ложном состоянии, когда вы выходите из функции. Может быть, вы могли бы сделать что-то вроде:

sub search {
  my $arr = shift;
  grep { !( /start/ || $_ eq "my magic reset string" ..
            /never_exist/ || $_ eq "my magic reset string" ) } 
      (@$arr, "my magic reset string");
}
...