How do I use an index in an array reference as a method reference in Perl?

◇◆丶佛笑我妖孽 提交于 2020-02-02 02:45:26

问题


Similar to this question about iterating over subroutine references, and as a result of answering this question about a OO dispatch table, I was wondering how to call a method reference inside a reference, without removing it first, or if it was even possible.

For example:

package Class::Foo;
use 5.012;   #Yay autostrict!
use warnings;

# a basic constructor for illustration purposes....
sub new { 
    my $class = shift;
    return bless {@_}, $class;
}

# some subroutines for flavor...
sub sub1 { say 'in sub 1'; return shift->{a} }
sub sub2 { say 'in sub 2'; return shift->{b} }
sub sub3 { say 'in sub 3'; return shift->{c} }

# and a way to dynamically load the tests we're running...
sub sublist {
    my $self = shift; 
    return [
        $self->can('sub1'),
        $self->can('sub3'),
        $self->can('sub2'),
    ];
}

package main;

sub get_index { ... } # details of how we get the index not important    

my $instance = Class::Foo->new(a => 1, b => 2, c => 3);
my $subs = $instance->sublist();
my $index = get_index();

# <-- HERE

So, at HERE, we could do:

my $ref = $subs->[$index];
$instance->$ref();

but how would we do this, without removing the reference first?

Edit:

Changed code example so people don't get hung up on implementation details (sigh, tried my best). The important difference between this and the first link I gave was that the function should be invoked as a method, not as a straight subroutine.

Edit 2:

See the discussion in the linked comment about the technical details, and why the longer way (storing the subref to a variable, then calling it) is probably preferable.


回答1:


As written, you can get away with

$tests->[$index]();

because the methods in your question aren't using $self.

You could pass $instance explicitly, but that's clunky. Better would be to simulate delegates with closures:

sub sublist {
    my $self = shift;
    my $sublist;
    for (qw/ sub1 sub3 sub2 /) {
      my $meth = $_;
      push @$sublist => sub { $self->$meth() };
    }
    return $sublist;
}

If you prefer to be concise, use

sub sublist {
    my $self = shift;
    return [ map { my $meth = $_; sub { $self->$meth() } }
             qw/ sub1 sub3 sub2 / ];
}

Calling one at random is still

$tests->[$index]();

but now the methods get invocants.


Update

Grabbing subrefs via can appears to be unnecessary complexity. If a runtime-determined list of names of methods to call will do, then you can simplify your code greatly:

sub sublist {
    my $self = shift; 
    return [ qw/ sub1 sub3 sub2 / ];
}

Below, we call them all for testing purposes, but you can also see how to call only one:

foreach my $method (@$subs) {
  my $x = $instance->$method();
  say "$method returned $x";
}

Output:

in sub 1
sub1 returned 1
in sub 3
sub3 returned 3
in sub 2
sub2 returned 2



回答2:


(Temporary placeholder here until/unless the original poster of the answer returns):

The trick is adding a dereference:

$instance->${\$sublist->[$index]}(@args);

thus you can also do:

$instance->${\$instance->sublist->[$index]}(@args);

otherwise it thinks it's a scalar to dereference. (eg, Not a SCALAR reference at script.pl, line XX).



来源:https://stackoverflow.com/questions/2804109/how-do-i-use-an-index-in-an-array-reference-as-a-method-reference-in-perl

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!