3

以下 perl 代码在 PerlCritic 中生成警告(由 Activestate 提供):

sub natural_sort {
    my @sorted;
    @sorted = grep {s/(^|\D)0+(\d)/$1$2/g,1} sort grep {s/(\d+)/sprintf"%06.6d",$1/ge,1} @_;
}

生成的警告是:

不要在列表函数中修改 $_

有关该警告的更多信息在这里

我不明白这个警告,因为我不认为我在修改 $_,尽管我想我必须这样做。有人可以向我解释一下吗?

4

5 回答 5

10

您的两个greps 都在修改$_,因为您正在使用s//. 例如,这个:

grep {s/(^|\D)0+(\d)/$1$2/g,1}

与此相同:

grep { $_ =~ s/(^|\D)0+(\d)/$1$2/g; 1 }

我认为你最好不要使用map,因为你没有用你grep的 s 过滤任何东西,你只是grep用作迭代器:

sub natural_sort {
    my $t;
    return map { ($t = $_) =~ s/(^|\D)0+(\d)/$1$2/g; $t }
           sort
           map { ($t = $_) =~ s/(\d+)/sprintf"%06.6d",$1/ge; $t }
           @_;
}

那应该做同样的事情并使批评者保持安静。List::MoreUtils如果您想要一些比 plain 更好的列表运算符,您可能想看看map

于 2011-05-05T08:07:18.443 回答
3

您正在s///grep 中进行替换 ( ie ),这会修改$_ie 被 grep 的列表。

于 2011-05-05T07:53:13.683 回答
3

perldoc perlvar解释了这种情况和其他情况:

下面是 Perl 假设 $_ 的地方,即使你不使用它:

  • 以下功能:

abs,警报,chomp,chomp,chr,chroot,cos,定义,eval,exp,glob,hex,int,lc,lcfirst,长度,log,lstat,mkdir,oct,ord,pos,print,quotemeta,readlink, readpipe、ref、require、reverse(仅在标量上下文中)、rmdir、sin、split(在其第二个参数上)、sqrt、stat、study、uc、ucfirst、unlink、unpack。

  • 所有文件测试(-f , -d ),但 -t 默认为 STDIN。见-X

  • 模式匹配操作 m//、s/// 和 tr///(又名 y///)在不使用 =~ 运算符时使用。

  • 如果没有提供其他变量,则为 foreach 循环中的默认迭代器变量。

  • grep() 和 map() 函数中的隐式迭代器变量。

  • given() 的隐式变量。

  • 当一个操作的结果被自己测试为
    while测试的唯一标准时放置输入记录的默认位置。在一段
    时间测试之外,这不会发生。

于 2011-05-05T09:07:08.740 回答
2

许多人已经正确回答了s操作符正在修改$_,但是在即将发布的 Perl 5.14.0 中将会有操作符的新r标志s(即s///r),而不是就地修改将返回修改后的元素。在有效的 Perler中阅读更多信息。您可以使用perlbrew来安装这个新版本。

编辑:Perl 5.14 现已推出!公告 公告 Delta

这是 mu (使用map)建议的功能,但使用此功能:

use 5.14.0;

sub natural_sort {
    return map { s/(^|\D)0+(\d)/$1$2/gr }
           sort
           map { s/(\d+)/sprintf"%06.6d",$1/gre }
           @_;
}
于 2011-05-05T14:37:59.343 回答
1

其他答案错过的非常重要的部分是这条线

grep {s/(\d+)/sprintf"%06.6d",$1/ge,1} @_;

实际上是在修改传递给函数的参数,而不是它们的副本。

grep是一个过滤命令,$_代码块中的值是 中的值之一的别名@_@_反过来包含传递给函数的参数的别名,因此当s///运算符执行其替换时,将对原始参数进行更改。这在以下示例中显示:

sub test {grep {s/a/b/g; 1} @_}

my @array = qw(cat bat sat);

my @new = test @array;

say "@new";   # prints "cbt bbt sbt" as it should
say "@array"; # prints "cbt bbt sbt" as well, which is probably an error

您正在寻找的行为(应用修改$_列表副本的函数)已被封装为apply多个模块中的函数。我的模块List::Gen包含这样的实现。 apply自己写也相当简单:

sub apply (&@) {
    my ($sub, @ret) = @_;
    $sub->() for @ret;
    wantarray ? @ret : pop @ret
}

这样,您的代码可以重写为:

sub natural_sort {
    apply {s/(^|\D)0+(\d)/$1$2/g} sort apply {s/(\d+)/sprintf"%06.6d",$1/ge} @_
}

如果您使用重复替换的目标是执行一种应用了瞬态修改的原始数据,您应该研究一种称为Schwartzian 变换的 Perl 习语,这是实现该目标的更有效方法。

于 2011-05-05T14:25:37.697 回答