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

TLP*_*TLP 29 perl

(假设use strict; use warnings;整个问题.)

我正在探索使用sub.

sub bb { print @_; }
bb 'a';
Run Code Online (Sandbox Code Playgroud)

这按预期工作.括号是可选的,就像许多其他函数一样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.
Run Code Online (Sandbox Code Playgroud)

但这不是:

bb('a');
sub bb { print @_; }
Run Code Online (Sandbox Code Playgroud)

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

special_print;
my special_print { print $some_stuff }
Run Code Online (Sandbox Code Playgroud)

会导致此错误:

Bareword "special_print" not allowed while "strict subs" in use at t13.pl line 6.
Execution of t13.pl aborted due to compilation errors.
Run Code Online (Sandbox Code Playgroud)

缓解这一特定错误的方法是:

  • 放在子名称之前,例如 &special_print
  • 在子名称后面加上空括号,例如 special_print()
  • 预先声明special_printsub special_print在脚本的顶部.
  • special_print在子声明后调用.

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

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

Eri*_*rom 33

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

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

sub name {...}   ===   BEGIN {*name = sub {...}}
Run Code Online (Sandbox Code Playgroud)

这意味着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
Run Code Online (Sandbox Code Playgroud)

当您使用严格和警告时,裸字将不会转换为字符串,因此第一个示例是语法错误.

预先声明时具有以下任何一项:

sub bareword;
use subs 'bareword';
sub bareword {...}
BEGIN {*bareword = sub {...}}
Run Code Online (Sandbox Code Playgroud)

然后标识符将解释如下:

bareword      ===   &bareword()     # compile time binding to &bareword
bareword LIST ===   &bareword(LIST) # same
bareword()    ===   &bareword()     # same
&bareword     ===   &bareword       # same
&bareword()   ===   &bareword()     # same
Run Code Online (Sandbox Code Playgroud)

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

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

也就是说,你很难找到一种语言,它可以让你调用子程序.这使您可以找到最适合您的方法.在我自己的代码中,如果我需要在声明它之前调用子程序,我通常会使用name(...),但是如果那个子程序有一个原型,我会把它称为&name(...)(并且你会得到一个警告"子程序调用太早,无法检查原型"如果你不这样称呼它).

  • 我发现'&name`纯粹是邪恶的*因为*它可以用来绕过原型,大多数初学者都没有意识到这一点.IMO`name(args)`更好. (3认同)
  • Nit#2:你的列表不考虑`BAREWORD BAREWORD`(间接方法调用),`BAREWORD - >`(类方法调用),`BAREWORD =>`(胖逗号自动调整),`print BAREWORD ...` (`*`原型和类似),当然还有其他人. (2认同)

Dav*_* W. 17

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

Perl从该语言的五个不同版本中获得了很多不和谐.Perl 4和Perl 5做了一些重大改变,这些改变可能导致以自由流动的方式编写的早期程序出现问题.

由于历史悠久,以及Perl具有和可以工作的各种方式,Perl很难理解正在发生的事情.当你有这个:

b $a, $c;
Run Code Online (Sandbox Code Playgroud)

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

当Perl编译器看到这个:

b($a, $c);
Run Code Online (Sandbox Code Playgroud)

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

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

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

b $a, $c;
Run Code Online (Sandbox Code Playgroud)

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

Perl 6的原因之一是从旧版本的Perl中删除大部分行李,并删除这样的奇怪事情.

顺便说一下,永远不要使用Perl Prototypes来解决这个限制.使用use subs或预先声明一个空白子程序.不要使用原型.

  • @TLP - Perl程序从上到下解析,就像许多脚本语言一样,使用一次通过解析器.这意味着,在看到子程序条目之前,它会看到`b $ a,$ c`.此时,可能有多种方式来解释这一行.是的,答案在下面,但解析器还不知道.在Perl 6中,这将被解决 - 不是因为解析器将进行两次传递,而是因为Perl 6将不允许使用裸字符串.因此,`b`必须是子程序. (3认同)

Eug*_*ash 6

如果子例程已预先声明,则括号是可选的.这在perlsub中有记录.

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

  • @TLP因为如果它的表现不同,它将是一种不同的语言. (2认同)
  • @TLP你在胡说八道.如果它被声明,那么它的工作原理.如果它没有被宣布那么它就不会. (2认同)
  • 因为那时候没有声明`bb`.使用括号,它仍然是明确的子调用,因此perl将其编译为`*bb {CODE}`上的一个enterub,它将在遇到`bb`的定义后在运行时成功解析(它**不**表示此时编译了`bb`).如果没有括号但是看到了'sub bb`定义,perl仍会进行子调用(在perl 5中添加了一个行为).但是如果没有parens并且没有范围的定义,它通过使perl-1兼容的假设'bb`是一个单词字符串文字来解决模糊性. (2认同)