我是函数式编程的忠实粉丝,所以当我在Perl中发现块引用时,我开始大量使用它们。
但是,我编写的以块为参数的函数是用这种方式编写的:
sub mygrep (&@) {
my $code = shift;
my @result;
foreach $_ (@_) {
push(@result, $_) if &$code;
}
@result;
}
(来自http://perldoc.perl.org/perlsub.html#Prototypes)
本质上,我的大多数函数都设置$_
,以便代码块可以访问我的sub中的数据。我想我的问题可以分为三个子问题:
local
ize $_
更好吗? 我仍然是一个Perl新手,所以任何答案和建议都表示赞赏 - 提前感谢! :)
答案 0 :(得分:5)
在您编写的代码中:
sub mygrep (&@) {
my $code = shift;
my @result;
foreach $_ (@_) {
push(@result, $_) if &$code;
}
@result;
}
foreach
循环在每次循环迭代时隐式地本地化$_
变量。它是完全安全的(并且是将值正确地放入$_
的最快方法)。
我对上面代码的唯一要点就是每次执行&$code
时,它都可以访问源参数列表,这可能会导致错误。您可以按如下方式重写代码:
sub mygrep (&@) {
my $code = shift;
my @result;
foreach $_ (splice @_) {
push(@result, $_) if &$code; # @_ is empty here
}
@result;
}
以下是您可以编写该功能的其他几种方法:
sub mygrep (&@) {
my ($code, @result) = shift;
&$code and push @result, $_ for splice @_;
@result
}
sub mygrep (&@) {
my $code = shift;
# or using grep in our new grep:
grep &$code, splice @_
}
这些示例中的每一个都为其子例程提供了别名$_
,并进行了适当的本地化。
如果您对高阶函数感兴趣,我建议您在CPAN上查看我的模块List::Gen,它提供了许多高阶函数来操作实数和惰性列表。
use List::Gen;
my $list = filter {$_ % 2} <1..>;
# as a lazy array:
say "@$list[0 .. 5]"; # 1 3 5 7 9 11
# as an object:
$list->map('**2')->drop(100)->say(5); # 40401 41209 42025 42849 43681
zip('.' => <a..>, <1..>)->say(5); # a1 b2 c3 d4 e5
答案 1 :(得分:4)
如何使用$code->($arg)
?
sub mygrep (&@) {
my $code = shift;
my @result;
foreach my $arg (@_) {
push(@result, $arg) if $code->( $arg);
}
@result;
}
我没有对它进行过测试,但我认为这样可行,并且可以让你将其他参数传递给$code
。
更新:这看起来很有趣所以我继续进行测试。它工作正常,见下文(我非常不喜欢原型,所以我删除它,特别是因为它一直在抱怨@a不是阵列参考; - (
#!/usr/bin/perl
use strict;
use warnings;
sub mygrep {
my $code = shift;
my @result;
foreach my $arg (@_) {
push(@result, $arg) if $code->( $arg);
}
@result;
}
my @a= ( 1, 2, 3, 4, 5, 6);
print mygrep( sub { return shift() % 2 }, @a), "\n";
当然,这种思路的主要乐趣也在于生成代码;
#!/usr/bin/perl
use strict;
use warnings;
sub mygrep {
my $code = shift;
my $filter= shift;
my @result;
foreach my $arg (@_) {
push(@result, $arg) if $code->( $arg);
}
@result;
}
my @a= ( 1, 2, 3, 4, 5, 6, 7, 8, 9);
print mygrep( mod_filter( 3), @a), "\n";
print mygrep( mod_filter( 4), @a), "\n";
sub mod_filter
{ my( $filter)= @_;
return sub { ! (shift() % $filter) };
}
答案 2 :(得分:3)
本地化$_
绝对更好。 subref可以修改$_
的值,这些更改将传播到调用函数中。这不是mygrep()
案例中的问题,但可能存在于其他案例中。
答案 3 :(得分:3)
1。这种方法有一些重大缺陷吗?
my $_;
会隐藏您对包变量$_
的更改。从mygrep
内部,你无能为力。
&$code
非常特别。您需要&$code()
或$code->()
代替。
更改$_
会更改传递给mygrep
的参数。这在这里是不受欢迎的。
2。在设置之前本地化$ _是一个更好的主意吗?
for
提供了更好的本地化local
,但它也提供了不合需要的别名。
3。我应该使用部分应用的函数吗?
我不知道这意味着什么。
修正:
sub mygrep (&@) {
my $code = shift;
my @result;
for (@_) {
# Create copy so $_ can be modified safely.
for (my $s = $_) {
push @result, $_ if $code->();
}
}
return @result;
}
尽管如此,我认为mygrep
毫无意义,因为map
+ grep
已经更容易做到了。比较
mygrep { if ($_ % 2) { ++$_; 1 } else { 0 } } LIST
与
map { $_+1 } grep { $_ % 2 } LIST
您甚至可以合并map
和grep
。
map { $_ % 2 ? $_+1 : () } LIST