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.
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);";
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With