简体   繁体   English

Perl的触发器操作员是否被窃听? 它具有全局状态,我该如何重置它?

[英]Is Perl's flip-flop operator bugged? It has global state, how can I reset it?

I'm dismayed. 我很沮丧。 OK, so this was probably the most fun Perl bug I've ever found. 好的,所以这可能是我见过的最有趣的 Perl bug。 Even today I'm learning new stuff about Perl. 即使在今天,我也在学习关于Perl的新内容。 Essentially, the flip-flop operator .. which returns false until the left-hand-side returns true , and then true until the right-hand-side returns false keep global state (or that is what I assume.) 从本质上讲,触发器运营商..直到左手侧返回true,然后 ,直到右手边返回false保持全局的状态,这返回false(或者说是我承担。)

Can I reset it (perhaps this would be a good addition to Perl 4-esque hardly ever used reset() )? 我可以重置它(也许这将是Perl 4-esque几乎没有使用reset() )的一个很好的补充? Or, is there no way to use this operator safely? 或者,没有办法安全地使用此操作员?

I also don't see this (the global context bit) documented anywhere in perldoc perlop is this a mistake? 我也没有看到perldoc perlop任何地方记录的这个(全局上下文位)这是一个错误吗?

Code

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;

Spoiler 扰流板

$ perl test.pl
first shot
foo
bar
second shot

Can someone clarify what the issue with the documentation is? 有人可以澄清文档的问题是什么吗? It clearly indicates: 它清楚地表明:

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

There is some vagueness there about what "Each" means, but I don't think the documentation would be well served by a complex explanation. 关于“每个”的含义有一些含糊不清的含义,但我不认为复杂的解释可以很好地满足文档的要求。

Note that Perl's other iterators ( each or scalar context glob ) can lead to the same problems. 请注意,Perl的其他迭代器( each或标量上下文glob )可能会导致相同的问题。 Because the state for each is bound to a particular hash, not a particular bit of code, each can be reset by calling (even in void context) keys on the hash. 因为each状态each绑定到特定的散列,而不是特定的代码位,所以each都可以通过调用哈希上的(甚至在void上下文中) keys来重置。 But for glob or .. , there is no reset mechanism available except by calling the iterator until it is reset. 但对于glob.. ,除了通过调用迭代器直到它被重置之外,没有可用的重置机制。 A sample glob bug: 一个示例glob bug:

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: 

For the overly curious, here are some examples where the same .. in the source is a different .. operator: 对于过于好奇,这里有一些例子,其中相同..在源中是一个不同的 ..运算符:

Separate closures: 单独关闭:

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

Comment out the $x if 0 line to see that non-closures have a single .. operation shared by all "copies", with the output being 12 . 注释掉$x if 0行,看看非闭包有一个由所有“副本”共享的单个..操作,输出为12

Threads: 主题:

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

Threaded code starts with whatever the state of the .. had been before thread creation, but changes to its state in the thread are isolated from affecting anything else. 线程代码以线程创建之前的任何状态开始,但是线程中对其状态的更改被隔离而不会影响其他任何内容。

Recursion: 递归:

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

Each depth of recursion is a separate .. operator. 每个递归深度都是一个单独的..运算符。

The trick is not use the same flip-flop so you have no state to worry about. 诀窍不是使用相同的触发器,所以你没有担心的状态。 Just make a generator function to give you a new subroutine with a new flip-flop that you only use once: 只需创建一个生成器函数,为您提供一个新的子程序,只需使用一次新的触发器:

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

I also write about this in Make exclusive flip-flop operators . 我还在Make独有的触发器操作器中写了这个。

The "range operator" .. is documented in perlop under "Range Operators". “范围运算符” ..在记录perlop中下“范围操作符”。 Looking through the doucmentation, it appears that there isn't any way to reset the state of the .. operator. 通过doucmentation看,似乎没有任何方法可以重置..运算符的状态。 Each instance of the .. operator keeps its own state, which means there isn't any way to refer to the state of any particular .. operator. ..运算符的每个实例都保持自己的状态,这意味着没有任何方法可以引用任何特定的..运算符的状态。

It looks like it's designed for very small scripts such as: 它看起来像是为非常小的脚本设计的,例如:

if (101 .. 200) { print; }

The documentation states that this is short for 文档说明这是简称

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

Somehow the use of $. 不知何故使用$. is implicit there (toolic points out in a comment that that's documented too). 隐含在那里(工具在评论中指出,也记录了这一点)。 The idea seems to be that this loop runs once (until $. == 200 ) in a given instance of the Perl interpreter, and therefore you don't need to worry about resetting the state of the .. flip-flop. 这个想法似乎是这个循环运行一次 (至$. == 200 )在Perl解释器的给定实例,因此您不必担心复位的状态..触发器。

This operator doesn't seem too useful in a more general reusable context, for the reasons you've identified. 由于您已确定的原因,此运算符在更通用的可重用上下文中似乎不太有用。

A workaround/hack/cheat for your particular case is to append the end value to your array: 针对您的特定情况的解决方法/黑客/作弊是将最终值附加到您的数组:

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

This will guarantee that the RHS of range operator will eventually be true. 这将保证范围运营商的RHS最终成立。

Of course, this is in no way a general solution. 当然,这绝不是一般解决方案。

In my opinion, this behavior is not clearly documented. 在我看来,这种行为没有明确记录。 If you can construct a clear explanation, you could apply a patch to perlop.pod via perlbug . 如果你可以构建一个清晰的解释,你可以申请一个补丁perlop.pod通过perlbug

I found this problem, and as far as I know there's no way to fix it. 我发现了这个问题,据我所知,没有办法解决它。 The upshot is - don't use the .. operator in functions, unless you are sure you are leaving it in the false state when you leave the function, otherwise the function may return different output for the same input (or exhibit different behaviour for the same input). 结果是 - 不要在函数中使用..运算符,除非您确定在离开函数时将其保留为false状态,否则函数可能会为同一输入返回不同的输出(或者表现出不同的行为)相同的输入)。

Each use of the .. operator maintains its own state. 每个使用..运算符都保持自己的状态。 Like Alex Brown said, you need to leave it in the false state when you leave the function. 就像Alex Brown说的那样,当你离开这个函数时,你需要让它处于假状态。 Maybe you could do something like: 也许你可以这样做:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM