如何使用与 List::Util 中的 sort 或 reduce 相同的签名编写子代码

How to code a sub with the same signature as sort or reduce in List::Util

我希望能够像这样使用一个函数:

my @grouped = split_at {
      $a->{time}->strftime("%d-%b-%Y %H") ne 
      $b->{time}->strftime("%d-%b-%Y %H") 
} @files;

其中 split_at 根据函数将数组拆分为数组引用数组,如下所示:

sub split_at(&@) {
# split an array into an arrayrefs based on a function
    my $cb = shift;

    my @input = @_;

    my @retval = ( [  ] );

    $i = 0;
    while ($i <= $#input) {
        push @{$retval[$#retval]}, $input[$i];
        $i++;
        if (($i < $#input) && $cb->($input[$i-1], $input[$i])) { push @retval, [] }
    }
    pop @retval unless @{$retval[$#retval]};
    return @retval;
}

目前我只能这样称呼它:

my @grouped = split_at { 
          $_[0]->{time}->strftime("%d-%b-%Y %H") ne 
          $_[1]->{time}->strftime("%d-%b-%Y %H") 
} @files;

这里使用 Time::Piece.

按 mtime 小时对文件进行批处理

我正在尝试找出能够将其称为(简体)的方法:

my @foo = split_at { $a <=> $b } @foo;

sortList::Util::reduce

类似

我已经检查了 List::Util::PP 中要减少的代码以供参考,但我对它的理解还不足以将其移植到我的案例中。

您需要做的主要事情是将词法的第一个和第二个值作为 $a$b 分配给调用者的命名空间。 reduce 使用一个值执行此操作:

#     /- 1
#     |  /-3
#     |  |             /-4 
# 0   |  |             |
local *{"${pkg}::a"} = $a;
#      \-----------/
#             2

让我们快速看一下:

  1. local 覆盖此范围内的全局变量以及临时包含在其中的所有范围。因此,当我们调用回调时,该变量将具有不同的值。请记住 $a$b 是特殊的全局变量。

  2. 它使用 glob * 分配到该名称空间中的符号 table,它足够聪明地找到正确的 slot 在符号 table 中。想象一个抽屉柜,其中一个抽屉用于标量,一个用于数组,一个用于散列等等。您可能也见过这种安装 subs 的语法。

  3. {""} 语法允许从多个片段和其他变量中构建一个变量名。我们使用包和变量名 a 来获取,例如 main::a。这需要关闭 strict 'refs',否则 perl 会报错,所以代码中有一个 no strict 'refs'。 1 中的 * 表示我们将此 var 名称用作 glob 类型。

  4. 这与 1 类似,但这次使用标量槽。在这种情况下,我们不必禁用严格引用,因为它只是一个普通字符串,而且 Perl 认为这是安全的。这是告诉解释器变量名结束的语法。比较这两个:

    my $foo, $foo_bar;
    "$foo_bar_baz";    # variable doesn't exist error
    "${foo}_bar_baz";  # prints value of $foo and string _bar_baz
    "${foo_bar}_baz";  # prints value of $foo_bar and string _baz
    

    我们需要这个,这样我们就不会在包 pkg.

    中获取 $a 的值
  5. 我们将词法 $a 的引用分配给类型 glob 中从 1 开始的那个槽。这将是一个标量引用。本质上,我们的变量 $a 现在有了另一个名称 $pkg::a。当您说 use Foo 'bar'.

    时,这类似于将 sub 导入您的命名空间

看完之后,我们可以更新您的代码。

sub split_at(&@) {
    my $cb = shift;
    my @input = @_;

    my @retval = [];

    my $pkg = caller;
    my $max = $#input;

    for my $i (0 .. $max) {
        push @{$retval[$#retval]}, $input[$i];
        no strict 'refs';
        local *{"${pkg}::a"} = $input[$i];
        local *{"${pkg}::b"} = $input[$i + 1]; # autovivification!
        if (($i < $max) && $cb->()) {
            push @retval, [];
        }
    }
    pop @retval unless @{$retval[$#retval]};
    return @retval;
}


my @files = map { { foo => $_ } } qw/ a a b b c d /;

my @grouped = split_at {
          $a->{foo} ne
          $b->{foo}
} @files;

我已经为我们的示例简化了数据结构。

对于循环的每次迭代,我们都需要 $a$b。因为通过类型 glob 将下一个元素分配给我们正在查看的 $b 会导致自动生成,所以我不得不将循环类型更改为计数器并引入一个新变量 $max。我在构建它时 运行 进入了一个无限循环,因为它一直在 @input.

的末尾放置 undef 个元素

除此之外,代码几乎相同。我们不再需要向回调传递参数,我们需要 no strict 'refs'。您通常会为尽可能小的范围关闭 strict。我们还需要获取 caller 的命名空间,以便我们可以将变量放在那里。

您还需要注意一件事。 List::Util::PP sets up $a$b 在调用者的命名空间中,否则我们可能会导致警告,因此如果您想将此函数放入库中,您应该使用相同的代码。

sub import {
  my $pkg = caller;
 
  # (RT88848) Touch the caller's $a and $b, to avoid the warning of
  #   Name "main::a" used only once: possible typo" warning
  no strict 'refs';
  ${"${pkg}::a"} = ${"${pkg}::a"};
  ${"${pkg}::b"} = ${"${pkg}::b"};
 
  goto &Exporter::import;
}