为什么括号仅在子声明后可选?

时间:2011-05-13 13:36:54

标签: perl

(在此问题中假设use strict; use warnings;。)

我正在探索sub

的用法
sub bb { print @_; }
bb 'a';

这可以按预期工作。括号是可选的,就像许多其他函数一样,如print, open等。

但是,这会导致编译错误:

bb 'a';
sub bb { print @_; }

String found where operator expected at t13.pl line 4, near "bb 'a'"
        (Do you need to predeclare bb?)
syntax error at t13.pl line 4, near "bb 'a'"
Execution of t13.pl aborted due to compilation errors.

但这不是:

bb('a');
sub bb { print @_; }

同样,没有args的sub,例如:

special_print;
my special_print { print $some_stuff }

会导致此错误:

Bareword "special_print" not allowed while "strict subs" in use at t13.pl line 6.
Execution of t13.pl aborted due to compilation errors.

缓解此特定错误的方法是:

  • Put&在子名称之前,例如&special_print
  • 在子名称后面加上空括号,例如special_print()
  • Predeclare special_printsub special_print位于脚本顶部。
  • 在子声明后调用special_print

我的问题是,为什么这种特殊待遇?如果我可以在脚本中全局使用sub,为什么我不能以任何我想要的方式使用它?是否有以这种方式实施sub的逻辑?

ETA:我知道如何解决它。我想知道这背后的逻辑。

4 个答案:

答案 0 :(得分:33)

我认为你缺少的是Perl使用严格的一次通过解析器。它不扫描文件中的子程序,然后返回并编译其余部分。知道了这一点,下面描述了一次通过解析系统的工作原理:

在Perl中,声明子例程的sub NAME语法等同于以下内容:

sub name {...}   ===   BEGIN {*name = sub {...}}

这意味着sub NAME语法具有编译时效果。当Perl解析源代码时,它正在使用当前的一组声明。默认情况下,该集是内置函数。由于Perl已经知道这些,因此可以省略括号。

一旦编译器命中BEGIN块,它就会使用当前规则集编译块的内部,然后立即执行该块。如果该块中的任何内容更改了规则集(例如将子例程添加到当前命名空间),那么这些新规则将对解析的其余部分生效。

如果没有预先声明的规则,标识符将被解释如下:

bareword       ===   'bareword'   # a string
bareword LIST  ===   syntax error, missing ','
bareword()     ===   &bareword()  # runtime execution of &bareword
&bareword      ===   &bareword    # same
&bareword()    ===   &bareword()  # same

当你使用严格和警告时,裸字不会被转换成字符串,所以第一个例子是语法错误。

预先声明以下任何内容时:

sub bareword;
use subs 'bareword';
sub bareword {...}
BEGIN {*bareword = sub {...}}

然后标识符将解释如下:

bareword      ===   &bareword()     # compile time binding to &bareword
bareword LIST ===   &bareword(LIST) # same
bareword()    ===   &bareword()     # same
&bareword     ===   &bareword       # same
&bareword()   ===   &bareword()     # same

因此,为了使第一个示例不是语法错误,必须首先看到前面的子例程声明之一。

至于所有这些背后的原因,Perl有很多遗产。开发Perl的目标之一是完全向后兼容。在Perl 1中工作的脚本仍然可以在Perl 5中运行。因此,无法更改裸字解析的规则。

也就是说,你很难找到一种语言,它可以让你调用子程序。这使您可以找到最适合您的方法。在我自己的代码中,如果我需要在声明它之前调用子例程,我通常使用name(...),但是如果该子例程有原型,我将其称为&name(...)(你会得到的)如果不按照这种方式调用,则警告“子程序过早调用原型”。

答案 1 :(得分:17)

我能想到的最佳答案是Perl的编写方式。这不是一个令人满意的答案,但最终,这是事实。 Perl 6(如果它出来的话)不会有这个限制。

Perl在该语言的五个不同版本中有很多不和之处。 Perl 4和Perl 5做了一些重大改变,这可能会导致早期程序以自由流畅的方式出现问题。

由于历史悠久,以及Perl具有和可以使用的各种方式,Perl很难理解正在发生的事情。如果你有这个:

b $a, $c;

Perl无法知道 b 是否是一个字符串,只是一个裸字(在Perl 4中允许)或者 b 是一个函数。如果 b 是一个函数,则应该在解析程序的其余部分时将其存储在符号表中。如果 b 不是子程序,则不应将其放在符号表中。

当Perl编译器看到这个时:

b($a, $c);

它不知道函数 b 的作用,但它至少知道它是一个函数,并且可以将它存储在符号表中,等待稍后定义。

当您预先声明您的函数时,Perl可以看到:

sub b;   #Or use subs qw(b); will also work.

b $a, $c;

并且知道 b 是一个功能。它可能不知道函数的作用,但现在有 b 的符号表条目作为函数。

Perl 6的原因之一是从旧版本的Perl中删除大部分行李并删除这类奇怪的东西。

顺便说一句,永远不要使用Perl Prototypes来解决这个限制。使用use subs或预先声明一个空白子例程。 Don't use prototypes.

答案 2 :(得分:6)

仅当子例程已预先声明时,括号才是可选的。这在perlsub中有记录。

Perl需要在编译时知道裸字是子程序名还是字符串文字。如果你使用括号,Perl会猜测它是一个子程序名称。否则,您需要事先提供此信息(例如,使用subs)。

答案 3 :(得分:-2)

原因是拉里沃尔是语言学家,而不是计算机科学家。

计算机科学家:语言的语法应该简单明了。尽可能清楚。

  • 避免编译器的复杂性
  • 消除歧义来源

Larry Wall :人们与编译器的工作方式不同。该语言应该为程序员服务,而不是编译器。另见Larry Wall对三个virtues of a programmer的概述。