29

がっかりしました。さて、これはおそらく私が今までに見つけた中で最も楽しい Perlのバグでした。今日でも私はPerlについて新しいことを学んでいます。基本的に、左側がtrueを返すまでfalseを返し、次に右側がfalseを返すまでtrue..を返すフリップフロップ演算子は、グローバル状態を維持します(またはそれが私が想定していることです)。

リセットできますか(おそらく、これはほとんど使用されていない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
4

6 に答える 6

34

誰かがドキュメントの問題が何であるかを明確にすることができますか?それは明確に示しています:

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

「それぞれ」の意味については曖昧さがありますが、複雑な説明でドキュメントがうまく機能するとは思いません。

Perlの他のイテレータ(eachまたはスカラーコンテキストglob)が同じ問題を引き起こす可能性があることに注意してください。の状態eachは特定のコードではなく特定のハッシュにバインドされているため、ハッシュeachを呼び出すことで(voidコンテキストでも)リセットできます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

再帰の各深さは、個別の..演算子です。

于 2010-01-27T02:20:24.083 に答える
19

トリックは同じフリップフロップを使用しないので、心配する必要はありません。ジェネレーター関数を作成して、一度だけ使用する新しいフリップフロップを備えた新しいサブルーチンを作成します。

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

これについては、 「排他的なフリップフロップ演算子を作成する」にも書いています。

于 2010-01-27T04:05:11.160 に答える
7

「範囲演算子」..は、perlopの「範囲演算子」に記載されています。文書を見ると、オペレーターの状態をリセットする方法がないよう..です。..演算子の各インスタンスは独自の状態を保持します。つまり、特定の..演算子の状態を参照する方法はありません。

次のような非常に小さなスクリプト用に設計されているようです。

if (101 .. 200) { print; }

ドキュメントには、これはの略であると記載されています

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

どういうわけか、そこでの使用$.は暗黙的です(ツールはコメントでそれも文書化されていると指摘しています)。このループは、Perlインタープリターの特定のインスタンスで(まで) 1回実行されるため、フリップフロップ$. == 200の状態をリセットすることを心配する必要はありません。..

あなたが特定した理由から、この演算子は、より一般的な再利用可能なコンテキストではあまり有用ではないようです。

于 2010-01-27T00:08:46.477 に答える
7

特定の場合の回避策/ハック/チートは、配列に終了値を追加することです。

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

これにより、範囲演算子のRHSが最終的に真になることが保証されます。

もちろん、これは決して一般的な解決策ではありません。

私の意見では、この動作は明確に文書化されていません。明確な説明を作成できる場合は、perlop.podviaにパッチを適用できますperlbug

于 2010-01-27T00:22:38.720 に答える
2

私はこの問題を見つけました、そして私が知る限りそれを修正する方法はありません。結果は-..関数を離れるときにfalse状態のままにしておくことが確実でない限り、関数で演算子を使用しないでください。そうしないと、関数が同じ入力に対して異なる出力を返す可能性があります(または同じに対して異なる動作を示す可能性があります)入力)。

于 2010-01-27T00:15:21.907 に答える
1

演算子を使用するたびに..、独自の状態が維持されます。Alex Brownが言ったように、関数を終了するときはfalse状態のままにする必要があります。多分あなたは次のようなことをすることができます:

sub search {
  my $arr = shift;
  grep { !( /start/ || $_ eq "my magic reset string" ..
            /never_exist/ || $_ eq "my magic reset string" ) } 
      (@$arr, "my magic reset string");
}
于 2010-01-27T00:21:05.957 に答える