如何使用嵌套闭包作为List :: Util :: reduce的第一个参数?

时间:2016-03-26 15:44:15

标签: perl closures perl5

注意:这个问题中的闭包只是一个方便的例子;我实际工作的那个比这复杂得多。 IOW,请忽略这个关闭的细节;重要的是AFAICT,它指的是父范围内的词汇变量。

我想重新定义下面的foo,以便调用List::Util::reduce中的第一个参数替换为对嵌套闭包的引用。

use strict;
use warnings FATAL => 'all';
use List::Util;

sub foo {
  my ( $x, $y ) = @_;
  return List::Util::reduce { $y->[ $b ]{ $x } ? $a + ( 1 << $b ) : $a } 0, 0 .. $#$y;
}

我的第一次尝试是:

sub foo {
  my ( $x, $y ) = @_;
  sub z {
    our ( $a, $b );
    return exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a;
  }
  return List::Util::reduce \&z, 0, 0 .. $#{ $y };
}

...但这会产生警告Variable "$y" will not stay shared

之前我已经看到过这种错误,而我唯一知道的是将嵌套的sub定义替换为分配给变量的匿名子。因此,我尝试了这个:

sub foo {
  my ( $x, $y ) = @_;
  my $z = sub {
    our ( $a, $b );
    return exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a;
  };
  return List::Util::reduce( $z, 0, 0 .. $#{ $y } );
}

现在错误显示Type of arg 1 to List::Util::reduce must be block or sub {} (not private variable)

我真的不明白。为什么不支持将subref作为第一个参数传递给reduce

PS:以下工作正常:

sub foo {
  my ( $x, $y ) = @_;
  my $z = sub {
    our ( $a, $b );
    return exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a;
  };
  return List::Util::reduce { $z->( $a, $b ) } 0, 0 .. $#{ $y };
}

...但我真的想知道(a)使用subref作为List::Util::reduce的第一个参数有什么问题(例如,是否存在支持此变体的技术障碍?); (b)是否有更直接的方法(比{ $z->( $a, $b ) })传递给List::Util::reduce一个嵌套的闭包?

2 个答案:

答案 0 :(得分:4)

List::Util::reduce使用Perl 原型,它指定对调用中传递的参数进行编译时检查。传递包含子例程引用的标量变量无法在编译时验证,因此Perl不允许它

这也是解决方案的途径。使用&符号&调用子程序通常被认为是不好的做法,但在这种情况下,它将打败原型检查,这是必需的

sub foo {

  my ( $x, $y ) = @_;

  my $z = sub {
    our ( $a, $b );
    return exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a;
  };

  return &List::Util::reduce( $z, 0, 0 .. $#{ $y } ); # Defeat prototype
}

请注意,这通常是不可能的,因为原型可以执行强制数组或散列通过引用传递,或强制表达式上的标量上下文,并使用{{调用这样的子例程1}}也将禁用这些行为。但是&有一个reduce的原型,这意味着一个块或&@(此处sub { }部分是可选的)后跟一个标量列表,这是正常行为一个没有原型的子程序,所以唯一的作用是允许sub为子程序引用传递

答案 1 :(得分:3)

reduce的原型为&@。这意味着调用必须使用以下语法之一:

reduce BLOCK LIST
reduce sub BLOCK, LIST  # The first form is short for this.
reduce \&NAME, LIST
reduce \&$NAME, LIST    # Same as third form, but via reference (short form)
reduce \&BLOCK, LIST    # Same as third form, but via reference (long form)

您可以使用以下任何等效声明:

return reduce { exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a } 0, 0 .. $#$y;

# Long way of writing the first.
return reduce(sub { exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a }, 0, 0 .. $#$y);

my $z = sub { exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a };
return reduce(\&$z, 0, 0 .. $#$y);

您还可以选择覆盖原型。

my $z = sub { exists $y->[ $b ]{ $x } ? $a | ( 1 << $b ) : $a };
return &reduce($z, 0, 0 .. $#$y);