Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a variable as a method name in Perl

I have a perl script (simplified) like so:

my $dh = Stats::Datahandler->new(); ### homebrew module

my %url_map = (
    '/(article|blog)/' => \$dh->articleDataHandler,
    '/video/' => \$dh->nullDataHandler,
); 

Essentially, I'm going to loop through %url_map, and if the current URL matches a key, I want to call the function pointed to by the value of that key:

foreach my $key (keys %url_map) {
    if ($url =~ m{$key}) {
        $url_map{$key}($url, $visits, $idsite);
        $mapped = 1;
        last;
    }
}

But I'm getting the message:

Can't use string ("/article/") as a subroutine ref while "strict refs" in use at ./test.pl line 236.

Line 236 happens to be the line $url_map{$key}($url, $visits, $idsite);.

I've done similar things in the past, but I'm usually doing it without parameters to the function, and without using a module.

like image 265
Glen Solsberry Avatar asked Jan 26 '11 17:01

Glen Solsberry


People also ask

What does $@ mean in Perl?

In these cases the value of $@ is the compile error, or the argument to die.

What is $$ in Perl?

$$ - The process number of the Perl running this script. $0 - Contains the name of the program being executed.

What is a method in Perl?

Methods are basically a subroutine in Perl, there is no special identity of a method. Syntax of a method is the same as that of a subroutine. Just like subroutines, methods are declared with the use of sub keyword. The method takes an object or the package on which it is invoked as its first argument.

How do you call a function in Perl?

The word subroutines is used most in Perl programming because it is created using keyword sub. Whenever there is a call to the function, Perl stop executing all its program and jumps to the function to execute it and then returns back to the section of code that it was running earlier.


2 Answers

Since this is being answered here despite being a dup, I may as well post the right answer:

What you need to do is store a code reference as the values in your hash. To get a code reference to a method, you can use the UNIVERSAL::can method of all objects. However, this is not enough as the method needs to be passed an invocant. So it is clearest to skip ->can and just write it this way:

my %url_map = (
    '/(article|blog)/' => sub {$dh->articleDataHandler(@_)},
    '/video/'          => sub {$dh->nullDataHandler(@_)},
); 

This technique will store code references in the hash that when called with arguments, will in turn call the appropriate methods with those arguments.

This answer omits an important consideration, and that is making sure that caller works correctly in the methods. If you need this, please see the question I linked to above:

How to take code reference to constructor?

like image 76
Eric Strom Avatar answered Oct 20 '22 01:10

Eric Strom


You're overthinking the problem. Figure out the string between the two forward slashes, then look up the method name (not reference) in a hash. You can use a scalar variable as a method name in Perl; the value becomes the method you actually call:

 %url_map = (
      'foo' => 'foo_method',
      );

 my( $type ) = $url =~ m|\A/(.*?)/|;
 my $method = $url_map{$type} or die '...';
 $dh->$method( @args );

Try to get rid of any loops where most of the iterations are useless to you. :)


my previous answer, which I don't like even though it's closer to the problem

You can get a reference to a method on a particular object with can (unless you've implemented it yourself to do otherwise):

my $dh = Stats::Datahandler->new(); ### homebrew module

my %url_map = (
   '/(article|blog)/' => $dh->can( 'articleDataHandler' ),
   '/video/'          => $dh->can( 'nullDataHandler' ),
);

The way you have calls the method and takes a reference to the result. That's not what you want for deferred action.

Now, once you have that, you call it as a normal subroutine dereference, not a method call. It already knows its object:

BEGIN {
package Foo;

sub new { bless {}, $_[0] }
sub cat { print "cat is $_[0]!\n"; }
sub dog { print "dog is $_[0]!\n"; }
}

my $foo = Foo->new;

my %hash = (
    'cat' => $foo->can( 'cat' ),
    'dog' => $foo->can( 'dog' ),
    );

my @tries = qw( cat dog catbird dogberg dogberry );

foreach my $try ( @tries ) {
    print "Trying $try\n";
    foreach my $key ( keys %hash ) {
    print "\tTrying $key\n";
        if ($try =~ m{$key}) {
            $hash{$key}->($try);
            last;
            }
        }
    }
like image 36
brian d foy Avatar answered Oct 20 '22 01:10

brian d foy