首页 > 解决方案 > 假装一个自动加载的函数有一个块原型

问题描述

假设我有一个以 sub 作为参数的子例程。

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

然后我可以这样称呼它

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

如果我用适当的原型声明它,我也可以使用块语法。

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

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

现在,假设我的函数实际上并不存在并且是一个 AUTOLOAD,例如

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

我们仍然可以使用显式调用它sub

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

我们可以通过预先声明子程序来消除不必要的括号。

use subs 'foobar';

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

但是如果我的函数实际上不存在并且有原型,我就无法取回块语法。我希望能够做到这一点

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

假设我事先知道名称foobar并且可以根据需要预先声明(就像我对 所做的那样use subs 'foobar'),有什么方法可以说服 Perlfoobar接受块参数,尽管只能foobar通过 AUTOLOAD 访问?

注意:这不是生产代码,永远不会。我是出于好奇而询问并可能在编程难题/挑战中使用这样的技巧,因此可读性和可维护性不是问题。

标签: perl

解决方案


一个子的原型——尤其是&——影响调用的解析和编译方式。因此必须知道何时解析和编译调用。

所以唯一的选择是提前声明子。缺点是必须提前知道名称。

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 ));

输出:

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.

相关:Perl 如何解析未引用的裸词?


请注意,如果名称是动态获取的,只要您可以在编译时获取它,您就不会不走运。

BEGIN {
   my ($name, $proto) = ( "foo", '$@' );
   eval "sub $name($proto);";
}

推荐阅读