什么时候应该使用 & 来调用 Perl 子例程?

Posted

技术标签:

【中文标题】什么时候应该使用 & 来调用 Perl 子例程?【英文标题】:When should I use the & to call a Perl subroutine? 【发布时间】:2010-11-23 18:15:27 【问题描述】:

我听说人们不应该使用 & 来调用 Perl subs,即:

function($a,$b,...);
# opposed to
&function($a,$b,...);

我知道其中一个参数列表是可选的,但在哪些情况下适合使用 & 以及您绝对不应该使用它的情况?

当省略& 时,性能提升又是如何发挥作用的?

【问题讨论】:

【参考方案1】:

我经常滥用&,但主要是因为我在做奇怪的界面东西。如果您不需要这些情况之一,请不要使用&。其中大部分只是访问子程序定义,而不是调用子程序。都在perlsub。

    引用一个命名的子程序。这可能是大多数 Perlers 唯一常见的情况:

     my $sub = \&foo;
    

    类似地,分配给一个 typeglob,它允许您使用不同的名称调用子例程:

     *bar = \&foo;
    

    检查是否定义了子例程,就像在测试套件中一样:

     if( defined &foo )  ... 
    

    删除不应该常见的子例程定义:

     undef &foo;
    

    提供一个调度程序子程序,它唯一的工作就是选择要调用的正确子程序。这是我使用&调用子程序的唯一情况,当我期望调用调度程序很多很多次并且需要从操作中挤出一点性能时:

     sub figure_it_out_for_me 
        # all of these re-use the current @_
          if( ...some condition... )  &foo      
       elsif( ...some other...     )  &bar     
       else                           &default 
       
    

    使用当前参数堆栈跳转到另一个子程序(并替换调用堆栈中的当前子程序),这是调度中不常见的操作,尤其是在AUTOLOAD

     goto ⊂
    

    调用以 Perl 内置函数命名的子例程。 & 始终为您提供用户定义的。那是why we teach it in Learning Perl。你真的不想正常这样做,但这是& 的功能之一。

有些地方可以使用它们,但也有更好的方法:

    调用与 Perl 内置程序同名的子例程。只是没有与 Perl 内置同名的子例程。检查perlfunc 以查看您不应使用的内置名称列表。

    禁用原型。如果您不知道这意味着什么或为什么想要它,请不要使用&。一些黑魔法代码可能需要它,但在这些情况下,您可能知道自己在做什么。

    取消引用并执行子例程引用。只需使用-> 符号即可。

【讨论】:

你真的想做defined &foo吗,看起来__PACKAGE__->can('foo')更“现代”?我会对意见感兴趣 can 不会告诉您是否定义了子例程。它会告诉您一个名为 can 的子例程是否希望您认为它已定义,即使它不是。 另外,->can 遵循继承。不会告诉您子例程是否实际上在当前包中。【参考方案2】:

IMO,唯一有理由使用 & 的情况是,如果您正在获取或调用 coderef,例如:

sub foo() 
    print "hi\n";


my $x = \&foo;
&$x();

可以在大多数情况下绝对不应该使用它的主要时间是调用具有指定任何非默认调用的原型的子时行为。我的意思是一些原型允许重新解释参数列表,例如将@array%hash 规范转换为引用。因此,潜艇会期待这些重新解释已经发生,除非你竭尽全力手动模仿它们,否则潜艇将获得与它预期的完全不同的输入。

我认为主要是人们试图告诉你,你仍在用 Perl 4 风格编写,我们现在有一个更干净、更好的东西,称为 Perl 5。

关于性能,Perl 有多种优化子调用的方法,而 & 会失败,其中一种主要方法是内联常量。

还有一种情况是使用& 提供性能优势:如果您使用foo(@_) 转发子呼叫。使用&foofoo(@_) 快得多。我不会推荐它,除非您通过分析明确发现您需要该微优化。

【讨论】:

能否请您扩展一下关于“非默认调用行为”的部分? 除了,&$x() 并不是调用代码引用的最佳方式;您可能应该像其他任何引用一样取消引用它:$x->()。 我唯一一次在通话中使用& 是为了AUTOLOAD。但是,目的不是优化,而是在任何人想要进行回溯时清理调用堆栈。 另一个 & 禁用原型检查的推论:如果你有一个原型化的函数将 coderef 作为它的第一个参数(a la map()/grep()),并且你想传递那个函数标量变量中的 coderef,则必须在调用中使用 &。如果你不这样做,Perl 会抱怨,因为它需要函数名或用大括号括起来的就地函数作为第一个参数。 Robert... coderef-deref 是我在 Perl 5.004 中添加的(在 Chip Salzenberg 的帮助下)。一些遗留文档是在那之前编写的。【参考方案3】:

&subroutine() 表单禁用原型检查。这可能是也可能不是您想要的。

http://www.perl.com/doc/manual/html/pod/perlsub.html#Prototypes

原型允许您指定子例程参数的数量和类型,并在编译时检查它们。这可以提供有用的诊断帮助。

原型不适用于方法调用,或使用 & 前缀以老式风格进行的调用。

& 是引用或取消引用子例程或代码引用所必需的

例如

sub foo 
   # a subroutine


my $subref = \&foo; # take a reference to the subroutine

&$subref(@args);  # make a subroutine call using the reference.

my $anon_func = sub  ... ; # anonymous code reference
&$anon_func(); # called like this

Protypes 也不适用于子例程引用。

&subroutine 形式也用于所谓的magic goto 形式。

表达式goto &subroutine 使用@_ 的当前值将当前调用上下文替换为对指定子例程的调用。

本质上,您可以通过调用指定的子程序来完全切换对一个子程序的调用。这在 AUTOLOAD 块中很常见,其中可以进行延迟子例程调用,可能对 @_ 进行一些修改,但它看起来完全就像是对命名子程序的调用一样。

例如

sub AUTOLOAD 
    ...
    push @_, @extra_args; # add more arguments onto the parameter list
    goto &subroutine ; # change call another subroutine, as if we were never here

我想这可能对tail call elimination 有用。

见detailed explanation of this technique here

【讨论】:

【参考方案4】:

我已经阅读了反对使用“&”的论据,但我几乎总是使用它。它节省了我太多的时间。我花了很大一部分 Perl 编码时间来寻找代码的哪些部分调用了特定的函数。使用前导&,我可以立即搜索并找到它们。如果没有前导 &,我会得到函数定义、cmets 和调试语句,这通常会使我必须检查的代码量增加三倍才能找到我要查找的内容。

不使用'&'的主要好处是它可以让你使用函数原型。但是 Perl 函数原型可能会像阻止错误一样频繁地创建错误,因为它们会获取您的参数列表并以您可能意想不到的方式重新解释它,因此您的函数调用不再传递它字面上所说的参数。

【讨论】:

同意。在始终使用它的代码库中,& 可能很有用。唯一的缺点是不允许原型(如果你可以称之为缺点)或者可能不小心说&foo 而不是&foo() 或者不小心在常量上使用它并抑制常量内联。这些缺点不足以阻止它如果你选择

以上是关于什么时候应该使用 & 来调用 Perl 子例程?的主要内容,如果未能解决你的问题,请参考以下文章

Python - 调用 perl 作为子进程 - 等待完成其后台进程并打印到 shell

在 XS 中为 C 库注册多个 Perl 子引用

在 c 语言中调用函数时,啥时候应该添加“&”,啥时候不应该添加?

Perl的子程序

什么时候应该使用 JSF 组件,什么时候应该使用 html 标签? [关闭]

perl fork() exec() ,子进程变得狂野