Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pretending an Autoloaded Function has a Block Prototype

Tags:

perl

Let's say I have a subroutine that takes a sub as argument.

sub foobar {
    my $block = shift;
    $block->();
}

Then I can call it like so

foobar(sub { print "Hello :)\n" });

I can also use block syntax if I declare it with an appropriate prototype.

sub foobar(&) {
    my $block = shift;
    $block->();
}

foobar { print "Hello :)\n" };

Now, suppose that my function doesn't actually exist and is an AUTOLOAD, such as

sub AUTOLOAD {
    $AUTOLOAD =~ s/.*:://;
    if ($AUTOLOAD eq "foobar") {
        my $block = shift;
        $block->();
    } else {
        die("No such function $AUTOLOAD");
    }
}

We can still call it with an explicit sub

foobar(sub { print "Hello :)\n" });

And we can eliminate unnecessary parentheses by predeclaring the subroutine.

use subs 'foobar';

foobar sub { print "Hello :)\n" };

But I can't get the block syntax back if my function doesn't actually exist and have a prototype. I'd like to be able to do this

foobar { print "Hello :)\n" };

Assuming I know the name foobar in advance and can predeclare things as needed (as I did with use subs 'foobar'), is there any way I can convince Perl that foobar takes a block argument, despite only having access to foobar through an AUTOLOAD?

Note: This is not production code and never will be. I'm asking out of curiosity and to potentially use tricks like this in programming puzzles / challenges, so readability and maintainability is not a concern.

like image 492
Silvio Mayolo Avatar asked Jan 04 '21 18:01

Silvio Mayolo


1 Answers

A sub's prototype —especially &— affects how the call is parsed and compiled. It must therefore be known when the call is parsed and compiled.

So the only option would be the declare the sub in advance. The downside is that the name must be known in advance.

BEGIN { say "Compilation phase."; }
say "Execution phase.";

sub foo(&@);   # Declaration with prototype.

sub AUTOLOAD {
    our $AUTOLOAD =~ s/.*:://;
    say "Autoloading $AUTOLOAD.";

    my $sub = sub(&@) {
       my $block = shift;
       say "Calling \"block\".";
       $block->(@_)
    };

    no strict qw( refs );
    *$AUTOLOAD = $sub;
    goto &$AUTOLOAD;
}

foo { say "<@_>" } qw( a b c );   # ok.  Compiled as: foo(sub { say "<@_>" }, qw( a b c ));
foo { say "<@_>" } qw( a b c );   # ok.  Compiled as: foo(sub { say "<@_>" }, qw( a b c ));
bar { say "<@_>" } qw( a b c );   # BAD! Compiled as: do { say "<@_>" }->bar(qw( a b c ));

Output:

Compilation phase.
Execution phase.
Autoloading foo.
Calling "block".
<a b c>
Calling "block".
<a b c>
<>
Can't locate object method "bar" via package "1" (perhaps you forgot to load "1"?) at a.pl line 26.

Related: How does Perl parse unquoted bare words?


Note that you're not out of luck if the name is dynamically obtained, as long as you can obtain it at compile-time.

BEGIN {
   my ($name, $proto) = ( "foo", '$@' );
   eval "sub $name($proto);";
}
like image 62
ikegami Avatar answered Oct 16 '22 05:10

ikegami