Perl调用带括号和不带括号的方法

r1wp621o  于 2022-11-15  发布在  Perl
关注(0)|答案(5)|浏览(217)

一些Perl书籍建议在调用类方法时使用括号,说这有助于防止解析器猜测代码的意图。然而,我所见过的几乎所有Perl代码(包括cpan上的模块)在调用不带参数的方法时很少使用括号。
省略那些括号是正常的吗?还是我应该一直键入它们?
我写了一个小的测试代码来测量调用一个方法时带括号和不带括号之间的差异,它确实显示了一个只有两个方法的类在1%和2%之间的微小差异。我想如果类很大的话,这个差异可能会上升。
下面是我用来进行基准测试的测试脚本:

#!/usr/bin/perl

use Benchmark qw(:all);

{
    package Messages;

    sub new {
        my ($self) = @_;
        return bless {}, $self;
    }

    sub message {
        my ($self) = @_;
        return "Hello world";
    }

    sub another {
        my ($self) = @_;
        return "Another Hello world";
    }
}

my $class = Messages->new();

cmpthese(10_000_000, {
    'with   ()  ' => sub { $class->message() },
    'without    ()  ' => sub { $class->message },
});

这是性能指标评测的结果:

Rate       without ()       with ()    
without ()      3320053/s          --         -1%
with    ()      3338898/s          1%          --

我想,如果应用程序使用数百个模块,每个模块都有数百个方法,这些方法都是在没有括号的情况下调用的,这是否会导致速度上的巨大差异?
如果是的话,为什么每个人都不带括号编码呢?

brgchamk

brgchamk1#

1%的差异是系统噪音。两个版本编译成完全相同的字节码,所以它们之间不可能有系统性的差异。使用任何一个使代码更容易阅读的变体。
如果你想看看它编译成什么,你可以这样做:

perl -MO=Concise -E '$foo->bar()'
dgtucam1

dgtucam12#

我不确定1%的时间差是否有意义,而且我怀疑您是否能够在真实的程序中测量任何差异。
有些人认为在方法调用的末尾没有()看起来更整洁。这是足够的理由。你也会在函数中看到它。
对我来说,当我想暗示“这里不需要参数”时,我会尝试这样做。大多数情况下只使用属性getter等。
如果一个方法可以接受可选参数,而它们只是被默认化,那么我宁愿不这样做,这样我就可以区分“不需要参数”和“我没有提供参数,但可以提供”。

svujldwt

svujldwt3#

这里有一些历史。如果你不想读全部,知道()解决了一个特殊的解析问题,让Perl知道你使用的是一个子例程而不是一个简单的单词。我倾向于总是使用()来表示空参数列表。我很好奇你认为哪些Perl书籍提出了这个建议。
我是什么?
Perl使用符号来表示名称空间(和访问,但先把它放在一边)。标量有$,数组有@,等等。在Perl 5之前,子例程有&(沿着crypto context)。这是有区别的

&uses_current_stack;  # subroutine uses the current @_
&no_arguments();      # subroutine with no arguments

Perl 5在子例程调用之前取消了显式的&。这允许了另外两种可能性,其中一种现在是不明确的:

&uses_current_stack;  # subroutine uses the current @_
&no_arguments();      # subroutine with no arguments
bareword;             # maybe a subroutine?
subroutine();         # subroutine with no arguments

bareword可能是一个子程序,但也可能不是,它取决于在使用它之前发生的事情。
这个程序编译得很好,但是正如您在反编译中看到的,Perl并不完全知道foo是什么:

$ perl -MO=Deparse -e 'foo'
'???';
-e syntax OK

即使子例程定义稍后出现,也会出现同样的问题:

$ perl -MO=Deparse -e 'foo; sub foo { print 1 }'
'???';
sub foo {
    print(1);
}
-e syntax OK

如果你向前声明foo将是一个子例程名。现在解析器有一个提示,告诉你该怎么做,即使你从来没有真正定义过这个子例程。一个子例程在你调用它之前不需要定义,Perl是一种动态语言,因此定义可以在以后显示。你也可以向前声明子例程,但在使用之前显示它的定义(虽然大多数人把子程序定义放在程序的最下面,碍事):

$ perl -MO=Deparse -e 'use subs qw(foo); foo'
use subs ('foo');
foo();
-e syntax OK

$ perl -MO=Deparse -e 'sub foo { print 1 }  foo'
sub foo {
    print(1);
}
foo();
-e syntax OK

数量

关于Perl还有另外一件事要知道。你可以在子例程调用中去掉括号,Perl会试图判断下一个标记是否是它的参数。
考虑一个print,它有一个参数列表,所有这些都成为print的参数:

print 1, 2, 3, 4, 5;   # 12345

但是有些东西需要一定数量的参数,rand知道它只需要一个参数,以前有4的地方,现在有rand的结果,5仍然来自5的参数:

print 1, 2, 3, rand 4, 5; # 1232.764206311960175

对于许多人来说,使用括号来表示读者Perl已经知道的内容更容易阅读:

print 1, 2, 3, rand(4), 5; # 1232.764206311960175

现在考虑一些参数范围的函数,比如split,其中有要使用的模式、目标字符串和最大项数,但所有这些参数都是可选的。

split;                # split on whitespace on $_, unlimited
split /\h+/;          # split on horizontal whitespace on $_, unlimited
split /\h+/, $var;    # same, with $var
split /\h+/, $var, 2; # split into limited number of items
split /\h+/, $var, 2; # split into limited number of items
split /\h+/, $var, 2, $arg; # oops

现在,我们来看一下,我们打算如何处理split?事实证明这是一个陷阱,因为split抱怨太多参数:

print 1, 2, split /\./, '1.2.3.4', 4, 5;  # error

括号解决了以下问题:

print 1, 2, split( /\./, '1.2.3.4' ), 4, 5;

或者,您可能希望处理$_

print 1, 2, split( /\./ ), '1.2.3.4', 4, 5;

这看起来像是一个愚蠢的案例,但更复杂的例子归结为一件事。

关于方法

Perl方法总是需要用括号将非零参数列表括起来:

$invocant->method( 1, 2, 3 );

顺便说一句,Raku有一个方法冒号,可以把它后面的所有内容都作为参数。我一开始并不喜欢这个方法,但现在我在Perl 5中怀念它:

$invocant->method: 1, 2, 3;  # Raku

Perl不要求在方法的零参数周围使用括号,因此以下两种方法都可以:

$invocant->method();
$invocant->method;

但是,在->后面的是方法,参数放在括号里,没有猜测。

正在收尾

但是,你知道Perl 4有这个&,不使用括号做了奇怪的默认参数的事情,所以你用()来做空参数列表。而且,你要记住,有长范围的影响。所以有些人用()来表示他们想要什么。其他人更舒服地知道Perl要做什么。
由此可见,有些人喜欢在任何地方都做同样的事情,所以即使一个案例可能不需要,因为他们在其他地方做,即使不需要,他们也会做。其他人则不那么在意。
Learning Perl中,我们知道你会看到很多不同的东西,所以我们展示了很多不同的东西。有你写的代码,但也有你没有写但必须读的代码。

ldxq2e6h

ldxq2e6h4#

因为你可以。真的。我认为。Perl允许这么多,人们正在使用它。有时是好的,因为你可以在短序列中写非常复杂的东西,有时是坏的,因为它经常变得复杂(特别是对Perl的新手)。
这也取决于你的程序做什么。区别应该只是在编译时间(我假设)。通常情况下,编译时间是整个应用程序生命周期的一小部分,在那里它并不那么重要。

webghufk

webghufk5#

我认为你的测量中存在系统性误差。当我运行你的代码时,without ()每次都优于with ()。但当我在cmpthese中使用相同长度的字符串时,例如:

cmpthese(10_000_000, {
    'with    ()' => sub { $class->message() },
    'without ()' => sub { $class->message },
});

我得到了这些(相当预期的)结果:

Rate with    () without ()
with    () 3412969/s         --        -1%
without () 3460208/s         1%         --

                Rate without () with    ()
without () 2994012/s         --        -0%
with    () 3003003/s         0%         --

                Rate without () with    ()
without () 3278689/s         --        -1%
with    () 3300330/s         1%         --

                Rate with    () without ()
with    () 3039514/s         --        -2%
without () 3105590/s         2%         --

                Rate without () with    ()
without () 3267974/s         --        -3%
with    () 3378378/s         3%         --

所以我认为这只是关于最佳实践。

相关问题