Perl:将代码块作为参数的函数抽象

kknvjkwl  于 2022-11-15  发布在  Perl
关注(0)|答案(1)|浏览(144)

在我的测试中,我想添加一个函数,它可以对一段代码进行计时。这段代码就像一个Map,你可以在大括号中给予它。我使用了这个answer,它运行得很好。
Timer.pm

sub recordTime(&@)
{
    # https://stackoverflow.com/a/6101734/11365539
    my $code = \&{shift @_};
    my %opts = @_;

    $opts{name}  //= 'Time';
    $opts{fmt}   //= 'default';
    $opts{hires} //= 0;

    my $timing_sub = $opts{hires} ? \&CORE::time : \&Time::HiRes::time;

    my $start = $timing_sub->();
    $code->();
    my $end = $timing_sub->();

    my $output;
    if($opts{log})
    {
        $output = IO::File->new($opts{log}, '>>')
                    or carp("Failed to open $opts{log}. Outputting to STDOUT...");
    }
    if(not defined($output)) #if $opts{log} is undefined or IO::File->new() failed
    {
        $output = *STDOUT;
    }

    my $time_diff = $end - $start;
    $output->print("$opts{name}: ");

    # formatting time and printing it

    {
        no warnings 'numeric'; # perl complains that *STDOUT is not numeric
        if($output != *STDOUT)
        {
            $output->close();
        }
    }
}

1;

这段代码看起来像预期的那样工作。我可以这样使用它:

Timer::recordTime {
    runTests();
} (name => 'Run all tests', log => $timing_log);

我不想到处写log => $timing_log,所以我在另一个模块中添加了这个函数。
TesterInterface.pm

sub logTime(&@)
{
    my $code = \&{shift @_};
    my %opts = @_;

    Timer::recordTime {
        $code->();
    } (%opts, log => $timing_log);
}

所以我会这样使用它

TesterInterface::logTime {
    runTests();
} (name => "Run all tests");

这样会不断抛出Undefined subroutine &main::0 called之类的错误。在我看来,这似乎是在计算$code最后一行的隐式返回。我通过在代码块末尾添加say("hi")语句来测试这一点,该语句将返回1。然后我看到了Undefined subroutine &main::1 called。我试图通过将该行更改为\ say("hi")来避开它,但这产生了以下错误:Can't call method "logTime" on unblessed reference .我真的不知道如何修复这个问题。
我确实发现这样做是可行的,但我更愿意避免编写sub。我也不明白为什么这个方法可行,但代码块版本不行。

TesterInterface::logTime(sub {
    runTests();
}, name => 'Run all tests');

我在Windows中使用Perl v5.22.1

w41d8nur

w41d8nur1#

您没有提供问题的演示,但很明显,对Timer::recordTime的调用发生在编译Timer::recordTime的声明之前。
这会导致

Timer::recordTime { $code->(); } ( %opts, log => $timing_log );

被解析为等效于以下内容的间接方法调用:

do { $code->(); }->Timer::recordTime( %opts, log => $timing_log );

对了,

my $code = \&{shift @_};

是一种复杂的方式

my $code = shift;

前者除了sub ref之外还接受sub name,但原型不允许这样做。
顺便说一下,您的logTime增加了很多不必要的开销用途:

sub logTime(&@) {
   &Timer::recordTime( @_, log => $timing_log );
}

&会导致忽略原型。

相关问题