29

我很沮丧。好的,所以这可能是我发现的最有趣的 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
4

6 回答 6

34

有人可以澄清文档的问题是什么吗?它清楚地表明:

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

关于“每个”的含义存在一些模糊性,但我认为复杂的解释不会很好地服务于文档。

请注意,Perl 的其他迭代器(each或标量上下文glob)可能会导致相同的问题。因为 for 的状态each绑定到特定的散列,而不是特定的代码位,each所以可以通过调用(即使在 void 上下文中)keys散列来重置。但是对于globor ..,没有可用的重置机制,除非调用迭代器直到它被重置。一个示例 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";

我还在Make Exclusive Flip-flop Operators中写到了这一点。

于 2010-01-27T04:05:11.160 回答
7

“范围运算符”..记录在perlop中的“范围运算符”下。查看文档,似乎没有任何方法可以重置..操作员的状态。运算符的每个实例都..保持自己的状态,这意味着没有任何方法可以引用任何特定..运算符的状态。

看起来它是为非常小的脚本设计的,例如:

if (101 .. 200) { print; }

文档指出这是缩写

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

不知何故,其中的使用$.是隐含的(工具在评论中指出,这也被记录在案)。这个想法似乎是这个循环在 Perl 解释器的给定实例中运行一次(直到$. == 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

运算符的每次使用都..维护自己的状态。就像亚历克斯布朗所说,当你离开函数时,你需要让它处于错误状态。也许您可以执行以下操作:

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 回答