Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi all- What's the best way to call a set of functions when you have their names (but not references to them). I really want to avoid writing "one big switch". Here's the simplest kind of example:

file.txt:

  set 5
  add 2
  mul 2

interp.pl:

!/usr/bin/perl use strict; sub set { my ($var, $param) = @_; $var = $param; return $var; } sub add { my ($var, $param) = @_; $var = ($var + $param); return $var; } sub mul { my ($var, $param) = @_; $var = ($var * $param); return $var; } my $register = 0; while( <STDIN> ) { my ($command, $param) = split " ", $_; print $command, "->", $param, "\n"; if( $command eq 'set' ) { $register = set( $register, $param ); } elsif( $command eq 'add' ) { $register = add( $register, $param ); } elsif( $command eq 'mul' ) { $register = mul( $register, $param ); } else { die "Unknown operator: $command\n"; } print "currently: $register\n"; print "------\n"; }

I'm not *actually* trying to write an asm interpreter, but it serves as a good example of where I want to take a reasonably complicated action based on data that's outside of my control (so please don't say "eval( <perl> )" right away ;^). The functions that I'm calling, I'll want to put in a separate module (ie: ops.pm) to make maintenance easier, but that's something for the future. Please feel free to suggest creative ideas as well, as I'm kindof stuck.

Thanks!

--Robert

Replies are listed 'Best First'.
Re: Dynamically calling different functions?
by ctilmes (Vicar) on Jun 30, 2003 at 19:55 UTC
    Put the commands in a hash, something like this:
    my %commands = ( mul => \&mul, add => \&add, set => \&set, ... );
    Call them like this:
    $commands{$command}($register, $param);
Re: Dynamically calling different functions?
by Ovid (Cardinal) on Jun 30, 2003 at 19:58 UTC

    Using a hash for this is very handy.

    my %function = ( set => \&set, add => \&add, mul => \&mul ); # later: if (exists $function{ $command } ) { $function{command}->($register,$param); } else { die "No such function: ($command)"; }

    Cheers,
    Ovid

    Looking for work. Here's my resume. Will work for food (plus salary).
    New address of my CGI Course.

Re: Dynamically calling different functions?
by tadman (Prior) on Jun 30, 2003 at 20:07 UTC
    Like what ctilmes suggested, but with a twist:
    # Create a HASH which stores a mapping between function names # and the associated function references (\&). Add any other # functions to the quoted-word listing my %commands = map { $_ => \&$_ } qw[ set add mul ]; # ... while (<STDIN>) { my ($command, $param) = split(" ", $_); # Check that the function exists before calling it, could have a # bad $command being passed. Perhaps add a warning in an # else block? if ($command{$command}}) { $register = $command{$command}->($register, $param); } }
    It might look a bit weird at first, but the map call works on a single listing that doesn't have duplication. I've also used the $ref->() style of calling which doesn't use an ampersand, but is functionally the same as &$ref().
Re: Dynamically calling different functions?
by tilly (Archbishop) on Jun 30, 2003 at 21:15 UTC
    Another alternative to the ones suggested. Put the functions in their own package, and have them all become methods. (The difference being that the method will get the calling object as its first argument. If this doesn't make sense to you, then start with perlboot.)

    The reason to do that is that if $meth has the name of a method, then $obj->$meth(@args) will dynamically look up the method on the fly.

    You don't actually have to be using OO to do that, you can just use the fact that Perl's facilities for OO support don't tell functions and methods apart very well. The following bad hack should work just fine (albeit fairly slowly) for normal functions:

    use Carp; # time passes... sub get_func { my $func_name = shift; my $pack = caller(); UNIVERSAL::can($pack, $func_name) || croak("Function '$func_name' not found in package '$pack'"); } # elsewhere what you wanted above becomes $register = get_func($command)->($register, $param);
Re: Dynamically calling different functions?
by artist (Parson) on Jun 30, 2003 at 20:12 UTC
    Here is the code with the module:
    You can use different names of operation and subs here.
    package Register; use strict; sub new { my $class = shift; my $self = {_register => 0 }; bless $self, $class; } sub set { my $self = shift; $self->{_register} = shift; } sub add { my $self = shift; $self->{_register} += shift; } sub multiply { my $self = shift; $self->{_register} *= shift; } sub value { my $self = shift; return $self->{_register}; } package main; my $r = new Register; my $ops = { set => 'set', add => 'add', mul => 'multiply', }; while(<DATA>){ s/^\s+//; s/\s+$//; my ($operation,$param) = split; my $sub = $ops->{$operation}; $r->$sub($param); print $r->value,"\n"; } __DATA__ set 5 add 2 mul 2

    artist

      I confess, I had done some reading about all this stuff before... how does this relate to the "no strict 'refs';" stuff? I just tried out this code, and it appears as though you're getting around the:
      use strict; $name = "call_this_func"; &{$name}();

      ...restrictions (which I don't quite understand). Is it because you are calling a function of an object? Or is it because you are using the $a->() format? My perl-fu is weak, but I would appreciate some understanding. Thanks!

      --Robert

        Straight from the docs:

        strict subs' : This disables the poetry optimization, generating a compile-time error if you try to use a bareword identifier that's not a subroutine, unless it appears in curly braces or on the left hand side of the "=>" symbol.

        artist

Re: Dynamically calling different functions?
by Skeeve (Parson) on Jul 01, 2003 at 07:04 UTC
    how about 'eval'?

    Your loop would become:

    Update: Thanks to rediablo for showing me that "eval" is unwanted.

      Hi. I just wanted to let you know I downvoted your node, and give you a reason why. If you read the original question, the OP explicitly says he does not want to use eval. It's not just a subtle hint:

      data that's outside of my control (so please don't say "eval( <perl> )" right away ;^) [emphesis mine]

      I'm not trying to rub a downvote in your face, I just wanted to let you know my reasons. Please don't take it personally. =^)

      Update: glad to see I've been downvoted (if only very little... downvoted nonetheless) for being honest and straightforward. Maybe I should reconsider my strategy and simply snipe at people from the dark.

        I downvoted Skeeve's posting as well because it was promoting the wreckless use of eval and also completely ignored the request. It's jut not very valuable advice.

        I guess people just don't appreciate it much when you try and explain your motivation for downvoting. My token ++ might not count for much, but it's all I can do.
        Thanks for telling me.

        I really didn't see the eval-hint. Maybe I have to read more carefully. I also shouldn't stop when I think I have enough information when ther actually is more text ahead...

        I gave you ++ for being informative. I hate it when I get downvoted and don't know why.