1

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

Warpstar22
  • 567
  • 4
  • 19
  • `my $code = shift;`? – Shawn Jul 21 '22 at 22:12
  • Perhaps this is related to the [indirect object notation, which frequently causes problems?](https://stackoverflow.com/questions/32955172/what-is-indirect-object-notation-why-is-it-bad-and-how-does-one-avoid-it) – amon Jul 21 '22 at 22:23

1 Answers1

2

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.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Throwing in a `no indirect;` at the top may verify ikegami's suspicion. This would disallow that surprise. If you are on the latest perl, a `use v5.36;` gives you that for free. – brian d foy Jul 22 '22 at 05:28
  • @brian d foy `use v5.36;` will cause problems in this situation as it enable sub sigs – ikegami Jul 22 '22 at 13:00
  • I updated the question. Got a little messed up in trying to simplify my use case. Let me know if you need more info. Do you need to know how the modules are being included in the files? I did update my code to use your version with less overhead – Warpstar22 Jul 22 '22 at 13:24
  • Re "*I updated the question.*", None of the changes affects the answer. /// Re "*Let me know if you need more info.*", I don't need any more info. I already identified the problem. – ikegami Jul 22 '22 at 13:35
  • I assume you mean how it's compiling before seeing the prototype. Can I do something about that? I include my module at the top of the file – Warpstar22 Jul 22 '22 at 13:55
  • Do the modules load each other (directly or otherwise)? – ikegami Jul 22 '22 at 13:56
  • `TesterInterface.pm` loads `Timer.pm` and `Tester1.pm`. `Tester1.pm` which uses `logTime` loads `TesterInterface.pm`. This is with `use`. – Warpstar22 Jul 22 '22 at 17:33
  • TesterInterface.pm loads Tester1.pm, and Tester1.pm loads TesterInterface.pm. Therein lies the problem. One of the `use` effectively becomes a no-op. Avoid this. – ikegami Jul 22 '22 at 18:16