高阶函数的意外结果

时间:2014-04-16 17:05:26

标签: perl

我有一个更高阶的函数,它甚至可以映射数组中的位置值:

sub map_even(&@) {
    my $block = shift;
    my @res;
    for $i (0..$#_) {
        push @res, $i%2 ? $_[$i] : &$block($_[$i]);
     }
    @res;
}
print map_even {$_*$_} 1,2,3,4;

我希望输出为14316,但实际输出为

0204

为什么会发生这种情况,我该如何解决这个问题?是否可以对代码进行任何改进?

3 个答案:

答案 0 :(得分:4)

在您的匿名函数中,您必须通过$_[0](提示:@_数组)访问第一个输入参数。

use strict;
use warnings;

sub map_even(&@) {
    my $block = shift;
    my @res;
    for my $i (0..$#_) {
        push @res, $i%2 ? $block->($_[$i]) : $_[$i];
     }
    @res;
}
print join ",", map_even {$_[0]*$_[0]} 1,2,3,4;

输出

1,4,3,16

使用$_

sub map_even(&@) {
    my $block = shift;
    my @res;
    for my $i (0..$#_) {
        push @res, $i%2 ? $block->() : $_ for $_[$i];
        # or
        # local $_ = $_[$i];
        # push @res, $i%2 ? $block->() : $_;
     }
    @res;
}
print join ",", map_even {$_*$_} 1,2,3,4;

答案 1 :(得分:3)

map_even块中,您使用特殊的$_变量。但是,您必须在循环中设置它:

local $_ = $_[$i];
... $block->();

$_是一个全局变量,可以使用local运算符暂时覆盖。 $_与子例程参数无关。


关于别名:Perls formapgrep主要将$_别名为当前元素作为性能黑客,而不是因为这种行为特别需要。为了执行别名,您应该本地化包含*_变量的整个$_ typeglob,然后将别名目标的标量引用分配给glob:

local *_ = \$_[$i];

答案 2 :(得分:2)

我会解决这两种方式之一。

首先,使用List::Utilspairmap

use strict;
use warnings;

use List::Util qw(pairmap);

my @x = (1 .. 4);

my @result = pairmap {$a, $b**2} @x;

print "@result\n";

或者更简单地说,只需使用索引:

use strict;
use warnings;

my @x = (1 .. 4);

my @result = map {$_ % 2 ? $x[$_] ** 2 : $x[$_]} (0..$#x);

print "@result\n";

然而,如果你真的想要一个新的子,我只需设置一个触发器:

use strict;
use warnings;

sub map_even(&@) {
    my $block = shift;
    my $even = 1;
    map {($even ^= 1) ? $block->() : $_} @_;
}
print join " ", map_even {$_*$_} 1,2,3,4;

所有输出:

1 4 3 16