Perl-向Sub发送块时的最佳实践
我非常喜欢函数式编程,所以当我在Perl中发现块引用时,我开始大量使用它们 但是,我编写的以块为参数的函数是这样编写的:Perl-向Sub发送块时的最佳实践,perl,Perl,我非常喜欢函数式编程,所以当我在Perl中发现块引用时,我开始大量使用它们 但是,我编写的以块为参数的函数是这样编写的: sub mygrep (&@) { my $code = shift; my @result; foreach $_ (@_) { push(@result, $_) if &$code; } @result; } (来自) 本质上,我的大多数函数都设置了$\uuu,以便代码块能够访问我的sub中的数据
sub mygrep (&@) {
my $code = shift;
my @result;
foreach $_ (@_) {
push(@result, $_) if &$code;
}
@result;
}
(来自)
本质上,我的大多数函数都设置了$\uuu
,以便代码块能够访问我的sub中的数据。我想我的问题可以分为三个子问题:
local
ize$\uu化是一个更好的主意吗李>
我还是一个Perl新手,所以任何答案和建议都非常感谢-提前感谢!) 将
$本地化绝对更好。subref可以修改$的值,这些更改将传播到调用函数中。这在mygrep()
案例中不是问题,但在其他案例中可能存在问题。使用$code->($arg)
怎么样
我还没有对它进行测试,但我认为这是可行的,它可以让您向$code
传递额外的参数
更新:这看起来很有趣,所以我继续测试它。它工作得很好,见下文(我非常不喜欢原型,所以我删除了它,特别是当它不断抱怨@a不是数组引用时--(
当然,这种思路的主要乐趣也是生成代码
#!/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) };
}
在您编写的代码中:
sub mygrep (&@) {
my $code = shift;
my @result;
foreach $_ (@_) {
push(@result, $_) if &$code;
}
@result;
}
foreach
循环在每次循环迭代中都隐式地本地化了$\ucode>变量。这是非常安全的(也是正确地将值输入$\ucode>的最快方法)
我对上面代码的唯一缺点是每次执行&$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 @_
}
这些示例中的每一个都为其子例程提供了一个别名$\ucode>,并进行了适当的本地化
如果您对高阶函数感兴趣,我建议您看看我在CPAN上的模块,它提供了几十个高阶函数来处理实列表和惰性列表
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
使用列表::Gen;
my$list=过滤器{$\u%2};
#作为惰性数组:
说“@$list[0..5]”#1 3 5 7 9 11
#作为一个对象:
$list->map('**2')->drop(100)->say(5);4040141209420254284943681
zip('.=>,)->说(5);#a1 b2 c3 d4 e5
1.这种方法是否存在一些主要缺陷?
查看该块时,my$\u;
将隐藏您对包变量$\ucode>的更改。从mygrep
内部无法执行此操作
map { $_ % 2 ? $_+1 : () } LIST
&$code
非常特殊。您需要&$code()
或$code->()
更改$\uu
将更改传递给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
和grep
map { $_ % 2 ? $_+1 : () } LIST
是的,这就是我想做的;我已经编写了一些函数,它们看起来像是在List::MoreUtils中找到的,在这里,模仿内置的语法(如grep和map)是很自然的。对于这些函数,我想我会继续使用$\ux,但对于我的其他函数,我认为$code->($arg)是最好的选择:)从可读性的角度来看,我不喜欢&$code
,它没有说“函数调用”。所以我会写$code->()
,但这可能是我个人的偏好。可读性是的,但是当放在循环的中心时,&$code
比$code->()
因为它避免了在每次循环迭代中将@
本地化为空列表。@Eric Strom很公平,你是对的。我仍然不喜欢;--)re“特别是当它不断抱怨@a不是数组引用时”,只有在原型中将@
更改为\@
时才会发生这种情况。感谢您提供有关List::Gen的提示!CPAN是如此巨大,有时很难找到好的部分。。。
map { $_+1 } grep { $_ % 2 } LIST
map { $_ % 2 ? $_+1 : () } LIST