我可以在Perl的硬编码地址调用子程序吗?

时间:2010-10-01 14:55:14

标签: perl eval

假设我有以下代码:

my $compiled = eval 'sub { print( "Hello World\n" ); }';

我可以这样写:

$compiled->();

到目前为止一切顺利。现在假设我创建了10个函数:

my @fns = ();
for ( my $i = 0; $i < 10; $i++ ) {
  push( @fns, eval "sub { print( 'I am function $i\n' ); }" );
}

我可以按如下方式调用这10个函数:

foreach ( @fns ) {
  $_->();
}

现在,我想创建一个动态函数,它可以显式调用我的10个函数中的每一个:

my $evalcode = "sub {";
foreach ( @fns ) {
    # if I print $_ it shows something like
    #   "CODE(0x94084f8)", but trying to
    #   call "CODE(0x94084f8)->()" is invalid
    $evalcode .= "$_->();";
}
$evalcode .= "}";


my $dynamic_fn = eval $evalcode;
$dynamic_fn->();

是否可以对字符串化引用并直接调用它?

PS问为什么,你问?因为我想编写一个动态例程来构造一个if ( m/.../ ) { } elsif ( m/.../ ) { } ...检查链,然后根据输入字符串调用动态函数。

5 个答案:

答案 0 :(得分:14)

您可能希望使用常规的词法闭包,而不是字符串evals:

my @functions;

for my $i (0 .. 9) {
    push @functions, sub { print "I am function $i\n" };
}

my $call_all_funcs = sub {
    for my $func (@functions) {
        $func->();
    }
};

$call_all_funcs->();

也可以根据地址检索代码引用,但这样做会更复杂,更难理解,而且通常不是一个好主意。

答案 1 :(得分:8)

如何使用闭包而不是eval字符串?

sub combine_subrefs {
  my @subs = @_;
  return sub { foreach my $subref (@subs) { $subref->() } };
}

my $dynamic_fn = combine_subrefs( @fns );
$dynamic_fn->();

我相信你也可以适应这种情况来做你提到的elsif事情。

答案 2 :(得分:7)

重新:为什么,你问?因为我想编写一个动态例程,构造一个if(m /.../){} elsif(m /.../){} ...链,然后根据输入字符串调用动态函数

创建 if&amp;像上面描述的elsif 链可以在不必诉诸eval的情况下完成:

use 5.012;
use warnings;

my $build_ifelse_dispatch_table = sub {
    my @functions = @_;

    sub {
        my $text = shift;
        for (@functions) {
            my ($re, $code) = @$_;
            if ($text =~ $re) {
                $code->();
                last;
            }
        }
    };
};

my $dispatch_on = $build_ifelse_dispatch_table->( 
    [ qr/tom/   => sub { say "Tom!"   } ],   # if    (m/tom/)   { ... }
    [ qr/dick/  => sub { say "Dick!"  } ],   # elsif (m/dick/)  { ... }
    [ qr/harry/ => sub { say "Harry!" } ],   # elsif (m/harry/) { ... }
);


$dispatch_on->( 'peeping tom'         );  # Tom!
$dispatch_on->( 'spotty dick pudding' );  # Dick!
$dispatch_on->( 'harry potter'        );  # Harry!
$dispatch_on->( 'Tom, dick and harry' );  # Dick!

参考:Dispatch Table上的维基百科条目。

/ I3az /

答案 3 :(得分:4)

当您打印子例程引用时,您会看到类似CODE(0xDEADBEEF)的内容,因为这是Perl对引用进行字符串化的方式。如果您打印任何不会使字符串化过载的引用,您会看到类似的事情:

 $ perl -le 'print []'
 ARRAY(0x1008032b8)

通常情况下,您无法真正使用该值,并且您看到的数字不一定与实际内存地址相对应。

对于您正在做的事情,请参阅 Mastering Perl 中的动态子例程章节。我会谈谈你可以做的不同的事情来组成子程序和使用匿名子程序。像Data::Constraint这样的模块甚至可能会给你一些想法。我在回答How can a Perl force its caller to return?时也谈到了这一点。

答案 4 :(得分:1)

有可能(见this similar problem and solutions),但也许有另一种方式。将字符串化代码引用映射到实际代码引用的(全局?)哈希怎么样?

my @fns = ();
for ( my $i = 0; $i < 10; $i++ ) {
  my $fn = eval "sub { print('I am function $i\n'); } ";
  if (ref $fn eq 'CODE') {
      $CODETABLE{$fn} = $fn;
  }
  push @fns, $fn;
}

...

my $evalcode = "sub {";
foreach ( @fns ) {
    # convert stringified code ref to the actual code ref
    $evalcode .= "\$CODETABLE{\"$_\"}->();";
}
$evalcode .= "}";

(eval $evalcode)->();

I am function 0
I am function 1
I am function 2
I am function 3
I am function 4
I am function 5
I am function 6
I am function 7
I am function 8
I am function 9