Оператор триггера Perl прослушивается? Он имеет глобальное состояние, как я могу сбросить его?
Я встревожен. Хорошо, так что это, наверное, самая забавная ошибка в 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 ответов
Может кто-нибудь уточнить, в чем проблема с документацией? Это ясно указывает на:
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
строка, чтобы увидеть, что non-замыкания имеют одну операцию.., общую для всех "копий", с выводом 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
Каждая глубина рекурсии - это отдельный.. оператор.
Хитрость заключается не в том, чтобы использовать один и тот же триггер, поэтому вам не о чем беспокоиться. Просто сделайте функцию генератора, чтобы дать вам новую подпрограмму с новым триггером, который вы используете только один раз:
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";
Я также пишу об этом в Make эксклюзивные операторы триггеров.
Обходной путь / хак / чит для вашего конкретного случая - добавить конечное значение в ваш массив:
sub search {
my $arr = shift;
grep { !( /start/ .. /never_exist/ ) } @$arr, 'never_exist';
}
Это будет гарантировать, что RHS оператора дальности в конечном итоге будет правдой.
Конечно, это никоим образом не является общим решением.
На мой взгляд, это поведение не четко задокументировано. Если вы можете создать четкое объяснение, вы можете применить патч к perlop.pod
с помощью perlbug
,
"Оператор дальности" ..
задокументировано в perlop в разделе "Операторы диапазона". Просматривая документацию, кажется, что нет никакого способа сбросить состояние ..
оператор. Каждый экземпляр ..
оператор сохраняет свое собственное состояние, что означает, что нет никакого способа сослаться на состояние какого-либо конкретного ..
оператор.
Похоже, он разработан для очень маленьких скриптов, таких как:
if (101 .. 200) { print; }
В документации говорится, что это сокращение от
if ($. == 101 .. $. == 200) { print; }
Каким-то образом использование $.
неявно там (инструментальные средства указывают на комментарий, который тоже задокументирован). Идея состоит в том, что этот цикл выполняется один раз (до $. == 200
) в данном экземпляре интерпретатора Perl, и поэтому вам не нужно беспокоиться о сбросе состояния ..
резкий поворот.
Этот оператор не кажется слишком полезным в более общем контексте многократного использования по причинам, которые вы определили.
Я нашел эту проблему, и, насколько я знаю, нет способа ее исправить. Результат - не используйте ..
оператор в функциях, если вы не уверены, что оставляете его в ложном состоянии при выходе из функции, в противном случае функция может возвращать разные выходные данные для одного и того же входа (или демонстрировать различное поведение для одного и того же входа).
Каждое использование ..
Оператор поддерживает свое собственное состояние. Как сказал Алекс Браун, вам нужно оставить это в ложном состоянии, когда вы выходите из функции. Может быть, вы могли бы сделать что-то вроде:
sub search {
my $arr = shift;
grep { !( /start/ || $_ eq "my magic reset string" ..
/never_exist/ || $_ eq "my magic reset string" ) }
(@$arr, "my magic reset string");
}