Keber
Keber

Reputation: 61

dynamic call to subroutines in perl

I'm a bit messed up with the following:

I have a function that calls subroutines in the following way:

sub someFunction {
    my $self = shift;
    my $type = $self->{'type'};

    if($type eq 'one_subroutine') {
        $self->updateOneSubroutine();
    }
    elsif($type eq 'another_one_sub') {
        $self->updateAnotherOneSub();
    }
(...)
    else {
        die "Unsupported '$type'";
    }

I have to change this to let each subroutine be coded in its own file, include all available files, and automagically call the subroutine inside.

I did this in a test file with the following code:

# Assume a routines subdir with one_subroutine.pm file with 
sub updateOneSubroutine(){
    $self = shift;
    $self->doSomeThings();

    (...) #my code
}
1;

test.pl

# Saves in routines hash_ref a pair of file_name => subRoutineName for each file in routines subdir.
# This will be used later to call subroutine.
opendir(DIR,"lib/routines") or die "routines directory not found";
for my $filename (readdir(DIR)) {
    if($filename=~m/\.pm$/){
        # includes file
        require "lib/routines/$filename";
        # get rid of file extension
        $filename=~s/(.*)\.pm/$1/g;
        my $subroutine = "update_${file}";
        # camelizes the subroutine name
        $subroutine=~s/_([a-z0-9])/\u$1/g;
        $routine->{ $filename }  = $subroutine;
    }
}

{
    no strict "refs";
    $routine->{$param}();
}

where param is something like "one_subroutine", that matches with a filename available.

Since each subroutine receives $self in the call, I should call the routine by $self->something();

I've tried $self->$routine->{$param}() , $self->${routine->${param}}() and many other things without success. I've checked chapter 9 "dynamic subroutines" of mastering perl, and a similar question to perl monks, but I can't still figure out how to reference the subroutine in a way that represents $self->updateAnotherOneSub() , or something similar that lets $self be read as a param in those subroutines.

Thanks in advance, Keber.

Upvotes: 0

Views: 3387

Answers (2)

Karsten S.
Karsten S.

Reputation: 2391

Try to extract the method name first, then it should work. I did a small test script that may do something like you want to, so:

my $method = $routine->{$param};
$self->$method->();

You can and of course should check, if the desired method exists like Eric said:

if ($self->can($method)) {
    $self->$method->();
}

The important part here is, that you extract the method name so you have it in a single variable; otherwise perl won't figure that out for you - and as far as I know there is no way of setting parens or braces to do so.

Upvotes: 0

Eric Strom
Eric Strom

Reputation: 40142

This seems a bit like an X/Y problem. What exactly are you trying to do? If it is to reduce loading time, then modules like AutoSplit/AutoLoader might be of interest to you.

If it is to create some sort of data structure of subroutines, you should be installing anonymous subs into a hash, rather than giving them all names.

Given a subroutine reference:

my $code = sub {...};

you would call it as:

$self->$code(...);

If instead you have a subroutine name, you can lookup the coderef:

my $code = 'Package::With::The::Subroutines'->can('method_name');

and if that succeeds (check it), then you can use $self->$code(...) to call it.


Given this code:

{
    no strict "refs";
    $routine->{$param}();
}

You would pass $self to the routine with:

{
    no strict "refs";
    $routine->{$param}($self);
}

Or you could approach it the way I did above with can:

'package'->can($routine->{$param})->($self)

if you don't want to turn off strict 'refs'

Upvotes: 3

Related Questions