【问题标题】:Anonymous subroutine call in PerlPerl 中的匿名子程序调用
【发布时间】:2017-09-25 14:03:42
【问题描述】:

尝试理解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 是已定义的字符串。

我所能理解的是,它是一个匿名子例程,由于 localtime 函数,它预计会返回多个值,但我就是无法理解这段代码遵循的机制。如果不让我知道,我想我已经提供了足够的信息来提出这个问题。

【问题讨论】:

  • @a@b$s 中的值示例会有所帮助。我可以告诉你它在做什么,但不能告诉你为什么。还有,这个怎么用?返回值本身就丢失了。这是分配给某个东西,还是另一个函数的返回值?
  • 我添加了几行使其更有意义,希望对您有所帮助
  • 已编辑。感谢您的发现

标签: perl


【解决方案1】:

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

此代码可能在子程序的末尾使用,因为它返回一个值列表。真正的代码可能是这样的。

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 文档也说这与系统无关。

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

sub 的实现很优雅,虽然不是 Perl 风格。但是,它完全没用,因为 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");

现在更有意义了。

【讨论】:

  • 据我所知-> 用于方法调用,调用者作为被调用函数的第一个参数,那么这里是如何工作的?就像sub{}->(localtime),我没有完全理解这个语法,有什么可以参考理解这个语法的吗?
  • 我看到了你的编辑,谢谢,所以它就像一个函数变量,用一些参数 function variable-->(arguments) 调用自己。如果上述代码中的 sub 有一个名称为 func ,则会产生更多问题,那么像 sub func {} ; func->(localtime) 这样的调用是否正确。同样在上面的代码中,为什么 sub 没有像 sub{}->(localtime()); 这样被调用,因为 localtime 是一个函数?以及您编辑中的变量 $code 如何将函数作为值保存 Perl 如何理解它。
  • @Vicky 你把事情搞混了。它是从匿名函数构造的代码引用。它没有分配给变量,而是直接调用。它永远不会在当前包中注册为具有名称的子,因此它是匿名的。像您刚刚展示的另一个函数范围内的命名 sub 不是有效的语法。您可以创建一个具有名称的实际 sub,然后调用它,但是您不会在函数中看到这两个数组。关于括号,它们在 Perl 中始终是可选的。 localtime 是内置函数,而不是函数。
  • 非常感谢,这对您有很大帮助!
猜你喜欢
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 2020-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多