我的子程序不会递归的原因

Reason my subroutine won't recurse

#!/usr/bin/perl
use strict;
use warnings;
use List::MoreUtils 'uniq';

my %functiontable =();
$functiontable{foo} = \&foo;

sub iterate {
  my ($function, $iterations, $argument) = @_;
  return $argument unless 0 < $iterations;
  return $argument unless $function = $functiontable{$function};
  my @functioned = $function->($argument);
  my @refunctioned = ();
  for my $i (0 .. @functioned - 1) {
    push @refunctioned, iterate ($function, ($iterations - 1), $functioned[$i]);
  }
  return uniq @refunctioned;
}

sub foo {
  my ($argument) = @_;
  my @list = ($argument, $argument.'.', $argument.',');
  return @list;
}

my @results = iterate 'foo', 2, 'the';
print "@results";

这会打印 the the. the,,即它不会迭代(递归)。我希望它打印 the the. the, the.. the., the,. the,,.

(我用Smart::Comments检查它是否第二次进入iterate,确实进入了,但它似乎并没有完成函数中的所有事情。)

我不明白为什么。有人可以帮我找出原因,或提出修复建议吗?

这一行:

return $argument unless $function = $functiontable{$function};

没有意义。在您的子例程 iterate 中,$function 是一个字符串,而 $functiontable{$function} 是对子例程的引用。我不确定这样做的目的是什么:是为了与存储的函数进行比较吗?是使用名字$function引用的函数吗?

假设是后者,在调用迭代时简单地传递对函数的引用会更有意义:

sub iterate {
  my ($function, $iterations, $argument) = @_;
  return $argument unless 0 < $iterations;

  my @functioned = $function->($argument);
  my @refunctioned = ();
  for my $i (0 .. @functioned - 1) {
    push @refunctioned, iterate ($function, ($iterations - 1), $functioned[$i]);
  }
  return uniq @refunctioned;
}

my @results = iterate($functiontable{foo}, 2, 'the');
print "@results";

输出:

the the. the, the.. the., the,. the,,

第一次调用子例程 iterate 时,它会将 $function 中的子例程名称从名称转换为子例程引用

所以第一次 iterate 调用自身时它正在传递子例程引用,并且行

return $argument unless $function = $functiontable{$function};

将引用字符串化并尝试使用类似于 CODE(0x23e0838)

的键来查找散列的元素

显然该元素不存在,因此您的 unless 失败并立即返回 $argument 而没有继续递归



更新

我会写这样的东西

#!/usr/bin/perl

use strict;
use warnings;
use 5.10.0;

my %functions = ( foo => \&foo );

sub iterate {
    my ($func, $arg, $depth) = @_;
    return $arg unless $depth;
    map {iterate($func, $_, $depth - 1); } $functions{$func}->($arg);
}

sub foo {
    my ($arg) = @_;
    map "$arg$_", '', '.', ',';
}

my @results = iterate('foo', 'the', 2);
say "@results";

输出

the the. the, the. the.. the., the, the,. the,,

问题出在这一行。

return $argument unless $function = $functiontable{$function};

变量 $function 正在改变用途并从字符串(函数名称)覆盖为代码引用(要执行的函数)。后来,它被传递到 iterate 中,它忠实地忽略了它。

有两件事可以改进此代码并避免此类问题。首先是不要改变变量的用途,使用两个变量。

return $argument unless $function_ref = $functiontable{$function_name};

现在错误不会发生了。您正在重新调整变量用途的一个重要指标是它会更改类型,例如从字符串更改为代码引用。

请注意,我完全放弃了 $function,因为它在这种情况下太笼统了。那是函数的名称还是函数的引用?两者都不明显,所以要明显。

最后,iterate 可以通过完全删除函数 table 来变得更加灵活。直接传入代码引用。如果你想要一个函数table,写一个包装器。

sub select_iteration {
    my($iteration_type, $iterations, $argument) = @_;

    my $iteration_code = $iteration_types{$iteration_type};
    return iterate($iteration_code, $iterations, $argument);
}