Perl中的匿名子程序调用

时间:2017-09-25 14:03:42

标签: perl

尝试按如下方式理解Perl中的一段代码

    my @a = qw(A B C D E F F A K O N D);
    my @b = qw(S F S T F F S);

$s=sprintf "%s %s %d %d:%d:%d %d: %s" ,   sub { ($a[$_[6]], $b[$_[4]], $_[3], $_[2], $_[1], $_[0], $_[5]+1900) }->(localtime), $s;

此代码位于包中的方法中,该方法已使用三个参数调用,已在此代码中的上述方法中定义了数组ab,并且s是一个定义字符串。

我可以理解的是,它是一个匿名子程序,由于本地时间函数,它应该返回多个值,但我无法理解该代码所遵循的机制。如果不让我知道,我认为我已经提供了足够的信息来提出这个问题。

1 个答案:

答案 0 :(得分:3)

语法是在一个语句中一次封装多个值的修改的一种不寻常的方式。

此代码可能在子例程结束时使用,因为它返回值列表。真正的代码可能看起来像这样。

sub foo {
    my @a = qw(Sun Mon Tue Wed Thu Fri Sat);
    my @b = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);

    my $s = q{foobar};

    #                  wday         month    mday   hour   min    sec    year
    return sub { ( $a[ $_[6] ], $b[ $_[4] ], $_[3], $_[2], $_[1], $_[0], $_[5] + 1900 ) }
        ->(localtime), $s;
}

让我们来看看。我添加了return,以便更清楚地了解发生了什么。

有一个匿名子例程sub { ... },它被定义并直接被称为sub { ... }->()。这就像一个lambda函数。此表单中的箭头运算符->用于调用代码引用。它与直接对象语法(例如$foo->frobnicate())的不同之处在于箭头后面没有方法名称。

my $code = sub { print "@_" };
$code->(1, 2, 3);

这将输出:

1, 2, 3

传递列表上下文中的localtime返回值。这是当前时间的一堆价值,正如in the documentation所述。

#     0    1    2     3     4    5     6     7     8
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
                                            localtime(time);

没有参数,localtime隐式使用time作为参数。在匿名子例程内部,这些值最终在@_中,这是子的参数列表。所以$_[0]是当前本地日期的秒部分。

然后有@a@b,这些都是非常糟糕的名字。如果它应该是简短的,我至少在工作日和月份称他们为@d@m

匿名子返回值列表。它是当前工作日,在@a数组中查找,其中星期日为索引0,当前月份为@b,日,小时,分钟和第二,年份为四位数(因此为+ 1900)。

整体函数foo返回此列表,另一个值$s作为其返回值列表的最后一个元素。

如果你要print所有这些,你会得到:

print join q{ }, foo();

__END__
Mon Sep 25 16 10 33 2017 foobar

sprintf的背景下,这变得更加明确。它可能会为日志条目添加时间戳。

sub log {
    my $s = shift;

    my @a = qw(Sun Mon Tue Wed Thu Fri Sat);
    my @b = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);

    #             0  1  2  3  4  5  6   7
    $s = sprintf "%s %s %d %d:%d:%d %d: %s",
        #        wday        month        mday   hour   min    sec    year
        #        0           1            2      3      4      5      6
        sub { ( $a[ $_[6] ], $b[ $_[4] ], $_[3], $_[2], $_[1], $_[0], $_[5] + 1900 ) }
        #              7
        ->(localtime), $s;

    return $s;
}

print &log('stuff happened'); # we need the & because CORE::log

我用sprintf模式中的参数位置标记了每个值。 $s似乎是日志消息,它被带有时间戳的新字符串替换,并且本身作为最后一个参数。

但是,这是一个非常不寻常的实现,因为localtime将在标量上下文中返回完全相同的内容。

print &log("stuff happened\n");
print ''.localtime . ": stuff happened\n";

__END__
Mon Sep 25 16:24:40 2017: stuff happened
Mon Sep 25 16:24:40 2017: stuff happened

它是 ctime(3)的返回值。我首先想到的可能是重新实现,因为在目标系统上不存在那种时间戳,但localtime文档也说这不依赖于系统。

  

此标量值的格式不依赖于语言环境,而是内置于   Perl的。对于GMT而不是当地时间使用gmtime内置。也可以看看   Time :: Local模块(用于转换秒,分钟,小时和   这样回到时间返回的整数值,以及POSIX   模块的strftime和mktime函数。

使用sub的实现很优雅,但不是非常好的Perl-ish。但是,它完全没用,因为Perl已经提供了完全相同的格式。

您可以使用此功能轻松替换我的log功能。

sub log {
    my $s = shift;

    $s = localtime . " $s";

    return $s;
}

但话说回来,如果@a@b可能是本地化的,那么这是有道理的。你给它们作为字母,这可能不是你真正的代码,但我可以看到这被用作例如将它翻译成德语。

use utf8;

sub log {
    my $s = shift;

    my @a = qw(So Mo Di Mi Do Fr Sa);
    my @b = qw(Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez);

    #             0  1  2  3  4  5  6   7
    $s = sprintf "%s %s %d %d:%d:%d %d: %s",
        #        wday        month        mday   hour   min    sec    year
        #        0           1            2      3      4      5      6
        sub { ( $a[ $_[6] ], $b[ $_[4] ], $_[3], $_[2], $_[1], $_[0], $_[5] + 1900 ) }
        #              7
        ->(localtime), $s;

    return $s;
}

print &log("stuff happened\n");

现在它更有意义。