阶乘递归达到零时出错

Tib*_*bor 1 error-handling recursion perl function parameter-passing

为什么Perl中的以下递归factorial失败,即使它在C++和Java中有效?

Java的:

public static long factorial(int n) {
  if (n == 0) {
    return 1;
  } else {
    return factorial(n-1) * n;
  }
}
Run Code Online (Sandbox Code Playgroud)

C++:

long factorial(int n) {
  if (n == 0)
    return 1;
  else
    return n * factorial(n-1);
}
Run Code Online (Sandbox Code Playgroud)

但是这个在Perl(use Carp;)中失败了:

sub factorial {
    my $n = shift || croak "null value for argument";
    return 1 if $n == 0;  # base case
    return  $n * factorial($n-1);
}
Run Code Online (Sandbox Code Playgroud)

错误消息(第10行是调用函数的地方main(),第16行是第二个return语句):

 null value for argument at ./fact1.pl line 14.
        main::factorial(0) called at ./fact1.pl line 16
        main::factorial(1) called at ./fact1.pl line 10
Run Code Online (Sandbox Code Playgroud)

(以下内容已经编辑,感谢Matt Jacob在原版中指出了我的遗漏.)

原始版本,没有鲤鱼,但有|| return,错误信息是:

Use of uninitialized value in multiplication (*) at ./fact1.pl line 16.
Run Code Online (Sandbox Code Playgroud)

这是在没有命令行参数时尝试使脚本保持静默的结果.(没有抱怨uninitialized value.)

对基本案例的一个小修改使它在Perl中也可以工作:

sub factorial {
    my $n = shift || croak "null value for argument";
    return 1 if $n == 1;  # base case
    return $n * factorial($n-1);
}
Run Code Online (Sandbox Code Playgroud)

(当然,代价是取消处理0(零)输入.)


得到教训

  1. 问题发生在我想到的不同位置
    • Perl代码在尝试比C++或Java代码片段做更多的部分中有错误(一种非常粗略的方法)
    • 不同的错误消息应该给我一个提示
  2. 以下可能有助于我将问题本地化:
    • 之后全部删除 shift;
    • 记住0(零)意味着false
  3. 逻辑定义 - 或者自版本5.10起存在于Perl中
  4. 至于正确的错误处理,此页面提供了一个很好的概述.

Sil*_*olo 6

对于被认为是真实的东西,Perl有一些相当不正统的规则.

特别是,当在布尔表达式中使用时,数字0被认为是假的.所以当你说这个

my $n = shift || croak "null value for argument";
Run Code Online (Sandbox Code Playgroud)

如果shift将0作为参数,则将其视为false,这意味着||运算符需要计算其第二个操作数.

幸运的是,Perl的编写者预见到了这种用例,因此他们定义了一个替代或运算符.而||检查是否所述第一值是truthy,//所述第一值检查是否定义.

my $n = shift // croak "null value for argument";
Run Code Online (Sandbox Code Playgroud)

这将按照您的意图行事,除非有可能$n是字面值undef.在这种情况下,你需要一些更复杂的内省来检查参数是否存在,但是为了这个阶乘函数的目的,这应该足够了.

  • @simbabque,你警告我们Perl实际上正在做我们想做的事情.当使用`或'时,分配不必要地发生,我们通过检查赋值的结果间接地检查我们想要检查的内容.当使用`||`或`//`时,检查需要检查的实际内容,并且*不会像`或`那样不必要地发生赋值*.这实际上是理想的情况,不是要警告的事情! (2认同)