tags:

views:

85

answers:

2

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.

+4  A: 

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
Greg Bacon
Updated the question. The fact that the subs aren't using self, and don't have arguments, are a side effect of the simplicity of the example and not an important implementation detail.
Robert P
Yep, that's another way :) I'm specifically looking for this way (and found! :) PBP has a lengthy explanation as to why using a string ref isn't a great idea. Simplified greatly, using a string lookup means Perl must look up the subref at run time. `can` lets you cache the lookup and returns undef if the method is missing--letting you do checks on your calls ahead of time. So, for example, if the methods returned in the list were based on user input, you could verify them before actually calling the function, and then iterate over them many times with only paying the lookup cost once.
Robert P
@Robert Are you actually running into performance trouble, or are you engaged in premature optimization?
Greg Bacon
I've been bitten by the string ref problem before; `can` is a nice way to check. Regarding the code, it actually wasn't even my code to begin with. It was a result of me being unable to fully answer the question I linked to about OO dispatch tables [here](http://stackoverflow.com/questions/2791833/how-do-i-implement-a-dispatch-table-in-a-perl-oo-module). They were specifically building up a dispatch array of functions they wanted to call. I didn't know how to invoke the method stored in another data structure - that was the topic I sought to learn from the question.
Robert P
Even though it wasn't really the way to solve the problem, you've got a great set of answers here, so I'll give ya the answer. :)
Robert P
A: 

(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).

Robert P