以下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} @_;
}
生成的警告是:
不要修改列表函数中的$ _
More info about that warning here
我不明白警告,因为我不认为我正在修改$ _,虽然我想我一定是。 有人可以向我解释一下吗?
答案 0 :(得分:10)
您的grep
正在修改$_
,因为您正在使用s//
。例如,这个:
grep {s/(^|\D)0+(\d)/$1$2/g,1}
与此相同:
grep { $_ =~ s/(^|\D)0+(\d)/$1$2/g; 1 }
我认为您最好不要使用map
,因为您没有使用grep
过滤任何内容,而只是使用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 }
@_;
}
那应该做同样的事情,让评论家保持沉默。如果您想要一些比普通map
更好的列表运算符,您可能需要查看List::MoreUtils
。
答案 1 :(得分:3)
你正在grep中进行替换(即s///
),这会修改$_
,即列表被grepped。
答案 2 :(得分:3)
以下是Perl的地方 假设$ _即使您不使用它:
- 以下功能:
abs,alarm,chomp,chop,chr,chroot, cos,defined,eval,exp,glob,hex, int,lc,lcfirst,length,log,lstat, mkdir,oct,ord,pos,print, quotemeta,readlink,readpipe,ref, 要求,反向(在标量上下文中 只有),rmdir,罪,分裂(在它上面) 第二个论点),sqrt,stat,study, uc,ucfirst,unlink,unpack。
除-t之外的所有文件测试(-f,-d),默认为STDIN。 见-X
模式匹配操作m //,s ///和tr ///(又名y ///)时 在没有=〜运算符的情况下使用。
如果没有其他变量,则foreach循环中的默认迭代器变量 提供。
grep()和map()函数中的隐式迭代器变量。
给定()的隐式变量。
操作结果时输入记录的默认位置 被自己作为唯一的测试 一时间测试的标准。在外面 在测试时,这不会发生。
答案 3 :(得分:2)
很多人都正确地回答了s
运算符正在修改$_
,但是在即将发布的Perl 5.14.0中,r
会有s
个标记。 1}}运算符(即s///r
)而不是就地修改将返回修改后的元素。阅读更多 The Effective Perler 。您可以使用 perlbrew 安装此新版本。
编辑:Perl 5.14现已推出! Announcement Announcement 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 }
@_;
}
答案 4 :(得分: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 transform的Perl习语,这是实现该目标的更有效方法