Reputation: 15978
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.
Upvotes: 6
Views: 245
Reputation: 139631
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.
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
Upvotes: 4
Reputation: 15978
(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
).
Upvotes: 0