【问题标题】:How do you create a callback function (dispatch table) in Perl using hashes?如何使用散列在 Perl 中创建回调函数(调度表)?
【发布时间】:2023-03-22 15:49:01
【问题描述】:

我想调用一个动态调度其他函数的主控制器函数,如下所示:

package Controller;

my %callback_funcs = ();

sub register_callback{
   my ($class,$callback,$options) = _@;
   #apppend to %callback_funcs hash ... ?
}

sub main{
%callback_funcs = ( add => 'add_func', rem => 'remove_func', edit => 'edit_func');  
  while(<STDIN>){
     last if ($_ =~ /^\s*$/);
     if($_ == 'add' || _$ == 'rem' || _$ == 'edit'){
        $result = ${callback_funcs['add']['func']}(callback_funcs['add']['options']);
     }
  }
}

sub add_func{
...
}

需要注意的是,subs 是在其他模块中定义的,因此回调必须能够引用它们......加上 我很难得到正确的哈希值!

【问题讨论】:

  • 第一件事:use strictuse warnings
  • callback_funcs 是一个散列,但 callback_funcs['add']['func'] 看起来不像散列查找。缺少印记,并且使用了错误的括号。
  • $_ == 'add' 执行数值比较。 'add' 并没有真正产生数字。这是马车。你想要$_ eq 'add'
  • 如果你想要更多,看看 Higher order perl.. 免费在线:hop.perl.plover.com/book 刚开始,看起来不错。

标签: perl function hash callback dynamic-function


【解决方案1】:

关于如何在单个文件中构建调度表并通过它调用函数,您已经得到了一些很好的答案,但您还一直在谈论希望在其他模块中定义函数。如果是这种情况,那么根据每个模块所说的可调度功能动态构建调度表,而不是担心手动更新它不是更好吗?当然会!

当然,演示这需要多个文件,我使用 CPAN 中的Module::Pluggable 来查找提供函数定义的模块。

dispatch_core.pl:

#!/usr/bin/env perl

use strict;
use warnings;

my %dispatch;

use lib '.'; # a demo is easier if I can put modules in the same directory
use Module::Pluggable require => 1, search_path => 'DTable';
for my $plugin (plugins) {
    %dispatch = (%dispatch, $plugin->dispatchable);
}

for my $func (sort keys %dispatch) {
    print "$func:\n";
    $dispatch{$func}->(2, 5);
}

DTable/Add.pm:

package DTable::Add;

use strict;
use warnings;

sub dispatchable {
    return (add => \&add);
}

sub add {
    my ($num1, $num2) = @_;
    print "$num1 + $num2 = ", $num1 + $num2, "\n";
}

1;

DTable/MultDiv.pm:

package DTable::MultDiv;

use strict;
use warnings;

sub dispatchable {
    return (multiply => \&multiply, divide => \&divide);
}

sub multiply {
    my ($num1, $num2) = @_;
    print "$num1 * $num2 = ", $num1 * $num2, "\n";
}

sub divide {
    my ($num1, $num2) = @_;
    print "$num1 / $num2 = ", $num1 / $num2, "\n";
}

1;

然后,在命令行上:

$ ./dispatch_core.pl 
add:
2 + 5 = 7
divide:
2 / 5 = 0.4
multiply:
2 * 5 = 10

现在添加新函数就像将新文件拖放到 DTable 目录中一样简单,并带有适当的 dispatchable 子目录。无需再碰dispatch_core.pl 即可再次添加新功能。

编辑:针对评论中关于是否可以在没有 Module::Pluggable 的情况下完成此操作的问题,这里有一个修改后的 dispatch_core.pl,它不使用除了定义可调度函数:

#!/usr/bin/env perl

use strict;
use warnings;

my %dispatch;

my @dtable = qw(
  DTable::Add
  DTable::MultDiv
);

use lib '.';
for my $plugin (@dtable) { 
    eval "use $plugin";
    %dispatch = (%dispatch, $plugin->dispatchable);
}   

for my $func (sort keys %dispatch) {
    print "$func:\n";
    $dispatch{$func}->(2, 5);
}   

【讨论】:

  • 这是一个了不起的例子,我喜欢你如何使用 dispatchable 作为 Dispatch Table 的接口函数——真的很棒。我想知道是否有办法在没有附加模块的情况下做到这一点。听起来 id 必须导出我想从其他模块调度的潜艇,你同意吗?
  • 如您所见,这样做时您不需要导出,因为 subs 仅在它们自己的包中通过名称引用。 Module::Pluggable 是少数几个提供“搜索此命名空间内的所有包”功能的模块之一,但您需要使用其中之一,除非您想在模块的主脚本中手动维护一个列表以从中加载可调度函数. (当然,或者您可以将模块定位函数复制到您自己的代码中,但此时您也可以只使用 M::P (或其他)...)
【解决方案2】:

也许这个简化的例子会有所帮助:

# Very important.
use strict;
use warnings;

# Define some functions.
sub multiply { $_[0] * $_[1] }
sub divide   { $_[0] / $_[1] }
sub add      { $_[0] + $_[1] }
sub subtract { $_[0] - $_[1] }

# Create a hash of references to those functions (dispatch table).
my %funcs = (
    multiply => \&multiply,
    divide   => \&divide,
    add      => \&add,
    subtract => \&subtract,
);

# Register some more functions.
sub register {
    my ($key, $func) = @_;
    $funcs{$key} = $func;
}

register('+', \&add);    # As above.
register('sum', sub {    # Or using an anonymous subroutine.
    my $s = 0;
    $s += $_ for @_;
    return $s;
});

# Invoke them dynamically.
while (<>){
    my ($op, @args) = split;
    last unless $op and exists $funcs{$op}; # No need for equality tests.
    print $funcs{$op}->(@args), "\n";
}

【讨论】:

  • 当潜艇来自其他模块时这是否也有效?
【解决方案3】:

因此,可以有一个包含匿名子例程的哈希,您可以从标准输入调用这些子例程。

my %callbacks = (
    add => sub {
        # do stuff
    },
    fuzzerbligh => sub {
        # other stuff
    },
);

您可以在哈希中插入更多的哈希值:

$callbacks{next} = sub {
    ...
};

你会像这样调用一个

$callbacks{next}->(@args);

或者

my $coderef = $callbacks{next};
$coderef->(@args);

您可以从 STDIN 或其他任何地方获取哈希键。

您也可以匿名定义它们,然后引用它们。

sub delete {
    # regular sub definition
}

$callbacks{delete} = \&delete;

不过,我不会调用这些回调。回调是在另一个子例程返回后调用的子程序。

您的代码也充斥着语法错误,这可能掩盖了这里更深层次的问题。我也不清楚您要对第二级数组做什么。你什么时候定义这些 subs,谁在什么时候使用它们,为了什么目的?

【讨论】:

  • 谢谢你的提示,我只是想把整个事情都包起来……我打扫干净,回来找更多! -- 但是如果回调的子程序是从外部模块加载/导出的呢?
  • 关于“你在你的例子中缺少一个箭头”,没有那个箭头是可选的。 perl -wE"$x{f}=sub{ say 'hi' }; $x{f}()"
  • @codeninja 在我的示例中使用最后一种方式引用它 $callback{delete} = \&External::Module::delete_me;
  • @ikegami:该箭头仅在 5.6 中成为可选箭头,比其他箭头晚,因此鲜为人知。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-23
  • 2017-02-06
相关资源
最近更新 更多