Perl - 向子块发送块时的最佳实践

时间:2011-10-12 13:02:57

标签: perl

我是函数式编程的忠实粉丝,所以当我在Perl中发现块引用时,我开始大量使用它们。

但是,我编写的以块为参数的函数是用这种方式编写的:

sub mygrep (&@) {
    my $code = shift;
    my @result;
    foreach $_ (@_) {
        push(@result, $_) if &$code;
    }
    @result;
}

(来自http://perldoc.perl.org/perlsub.html#Prototypes

本质上,我的大多数函数都设置$_,以便代码块可以访问我的sub中的数据。我想我的问题可以分为三个子问题:

  1. 这种方法有一些重大缺陷吗?
  2. 在设置之前local ize $_更好吗?
  3. 我应该使用部分应用的功能吗?
  4. 我仍然是一个Perl新手,所以任何答案和建议都表示赞赏 - 提前感谢! :)

4 个答案:

答案 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。这种方法有一些重大缺陷吗?
    鉴于该块,
  1. my $_;会隐藏您对包变量$_的更改。从mygrep内部,你无能为力。

  2. &$code非常特别。您需要&$code()$code->()代替。

  3. 更改$_会更改传递给mygrep的参数。这在这里是不受欢迎的。

  4. 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
    

    您甚至可以合并mapgrep

    map { $_ % 2 ? $_+1 : () } LIST