Reputation: 613
For my testing, I wanted to add a function that could time a block of code. The block of code would be like map where you give it in braces. I used this answer and it works well.
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;
This code seems to work as expected. I can use it like this:
Timer::recordTime {
runTests();
} (name => 'Run all tests', log => $timing_log);
I don't want to write log => $timing_log
everywhere, so I added this function in another module.
TesterInterface.pm
sub logTime(&@)
{
my $code = \&{shift @_};
my %opts = @_;
Timer::recordTime {
$code->();
} (%opts, log => $timing_log);
}
So I would use it like this
TesterInterface::logTime {
runTests();
} (name => "Run all tests");
This keeps throwing absurd errors such as Undefined subroutine &main::0 called
. To me it seems like this is evaluating an implicit return of the last line of $code
. I tested this by adding a say("hi")
statement at the end of the code block which will return 1. I then saw Undefined subroutine &main::1 called
. I tried to then escape it by changing the line to \ say("hi")
, but that produces this error: Can't call method "logTime" on unblessed reference
. I really have no idea how to fix this.
I did find that doing this will work, but I would prefer to avoid having to write sub
. I also don't understand why this method works, but not the code block version.
TesterInterface::logTime(sub {
runTests();
}, name => 'Run all tests');
I'm using Perl v5.22.1 in Windows
Upvotes: 1
Views: 87
Reputation: 385764
You have not provided a demonstration of the problem, but it's apparent the call to Timer::recordTime
occured was compiled before Timer::recordTime
's declaration was compiled.
This causes
Timer::recordTime { $code->(); } ( %opts, log => $timing_log );
to be parsed as an indirect method call equivalent to the following:
do { $code->(); }->Timer::recordTime( %opts, log => $timing_log );
By the way,
my $code = \&{shift @_};
is a complicated way of doing
my $code = shift;
The former does accept a sub name in addition to a sub ref, but the prototype doesn't allow that.
By the way, your logTime
adds a lot of unnecessary overhead. I'd use:
sub logTime(&@) {
&Timer::recordTime( @_, log => $timing_log );
}
The &
causes the prototype to be ignored.
Upvotes: 2