我可以在 Perl 中传递对象方法的代码引用吗?

Can I pass the code reference of an object's method in Perl?

在处理各种获取和设置参数的网络处理程序中,我大量使用了闭包。 我有一个子例程,它接收一个闭包并使用作为参数传递给 return 的闭包构建另一个闭包(听起来很复杂,但这就是 为什么 我想要这样)。 现在我有一个情况,我必须传递两个非常相似的闭包,每个都使用相同的对象方法,但具有不同的参数(对象方法检查传递的参数数量)。

我的想法不是传递两个(或更多)类似的闭包,而是传递一个引用$meth_ref给对象的方法(对象也传递给函数returning闭包),这样函数就可以使用代码参考来传递不同的参数。

不幸的是我没有找到这样做的语法。

代码草图:

sub closure_maker($$)
{
    my ($obj, $meth_ref) = @_;

    return sub (...) {
        $meth_ref->($obj);
        ...
        $meth_ref->($obj, ...);
    };
}

my @handlers = (closure_maker($obj1, ???), closure_maker($obj2, ???));

希望你明白了。

您可以使用 \&Classname::method 获得对方法的引用,然后可以将其用于 class 的给定对象。例如:

#!/usr/bin/env perl
use strict;
use warnings;
use feature qw/say/;

package Example {
    sub new {
        my ($class, $name) = @_;
        return bless { name => $name }, $class;
    }

    sub method1 {
        my $self = shift;
        say "Method 1 of $self->{name}: @_";
    }
};

sub make_closure($$) {
    my ($obj, $method) = @_;
    return sub { $obj->$method(@_); }
}

my $obj1 = Example->new("Bob");
my $obj2 = Example->new("Cindy");
my $closure1 = make_closure($obj1, \&Example::method1);
my $closure2 = make_closure($obj2, \&Example::method1);
$closure1->(qw/1 2/);
$closure2->(qw/A B C/);

产出

Method 1 of Bob: 1 2
Method 1 of Cindy: A B C

如果您有疑虑,您可能希望对这些方法添加类型检查,以确保它们不会被错误的对象意外调用 class。在 perl 5.32 中添加的 isa 运算符使这变得简单:

# In package Example
use Carp;
sub method1 {
    use experimental qw/isa/;
    my $self = shift;
    croak "Invalid object; should be an Example" unless $self isa Example;
    say "Method 1 of $self->{name}: @_";
}

旧版本可以使用built-in isa方法:

# In package Example
use Carp;
use Scalar::Util qw/blessed/;
sub method1 {
    my $self = shift;
    croak "Invalid object; should be an Example"
        unless defined blessed $self && $self->isa("Example");
    say "Method 1 of $self->{name}: @_";
}

我找到了一个非常丑陋但有效的解决方案。 示例会话来自真实代码:

### this is the sample method:
  DB<5> x MessageLogFormat::log_appname($log_format)
0  ''
  DB<11> $xxxx = \&{*{MessageLogFormat::log_appname}}

  DB<12> x ref $xxxx
0  'CODE'
  DB<13> x $xxxx->($log_format)
0  ''
  DB<14> x $xxxx->($log_format, 1)
0  1
### NAME() just outputs the name of the class
  DB<17> $n = $log_format->NAME()
  DB<19> $xxx = \&{*{${n}.'::log_appname'}}
### the method is actually a closure (provided by class `Class`) by itself
  DB<20> x $xxx
0  CODE(0x1af8960)
   -> &Class::__ANON__[lib/Class.pm:85] in lib/Class.pm:78-85
  DB<21> x $xxx->($log_format)
0  1
  DB<22> x $xxx->($log_format, 0)
0  ''
  DB<23> x $xxx->($log_format)
0  ''
### So that worked; try a non-closure method, too:
  DB<24> $xxx = \&{*{${n}.'::new'}}
  DB<25> x $xxx
0  CODE(0x1afcb88)
   -> &MessageLogFormat::new in lib/MessageLogFormat.pm:85-91

在草拟的解决方案中,我不必知道(并明确写下)每个正在使用的对象的 class。

使用$obj->$method_name().

sub closure_maker($$)
{
    my ($obj, $method_name) = @_;

    return sub {
        $obj->$method_name();
        ...
        $obj->$method_name(...);
    };
}

my @handlers = map { closure_maker($_, "method_name") } $obj1, $obj2;

您可以使用 $obj->can 获取方法的引用。

sub closure_maker($$)
{
    my ($obj, $method_name) = @_;

    my $method_ref = $obj->can($method_name);

    return sub {
        $obj->$method_ref();
        ...
        $obj->$method_ref(...);
    };
}