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

Monks,

Suppose one a subroutine and a scalar that contains the name of the subroutine. How does one call the subroutine based on the content of the scalar?

use strict; use warnings; sub foo { print 'foo\n'; } sub bar { print 'bar\n'; } my $var = 'foo'; # invoke sub foo __END__

Update Tue Apr 15 07:32:23 CEST 2008: What I am actually trying to do is this:

I intend to create a script that takes two files as arguments: A (CSV) data file, and a (Config::IniFiles) configuration file. The data file will look like this:

# col1;col2;col3; 123456;hey hey;2008-04-14 123457;my my;2008-04-15
The configuration file will look like this:
col1=is_int col2=is_string col3=is_yyyy_mm_dd_date
Each key in the configuration file refers to a column in the data file. Each value corresponds to a subroutine that returns true or false depending on whether the data passes validation or not. The list of validation functions grows quickly, and I was hoping to avoid having to maintain an ever-growing list of if-else expressions or a giant hash that maps each string expression to the given subroutine. I am aware of the security risks, although I believe there can be measures put in place to ensure that only pre-defined subroutine can be called.

Any tips or hints on how to otherwise solve this problem is welcome.

--
Andreas

Replies are listed 'Best First'.
Re: Invoke sub whose name is value of scalar
by FunkyMonk (Bishop) on Apr 14, 2008 at 13:02 UTC
Re: Invoke sub whose name is value of scalar
by Erez (Priest) on Apr 14, 2008 at 14:06 UTC

    At some point in their life, every programmer decides that, for the problem at hand, the best solution is to create a set of functions and pass their names as variables. It's not.
    First, its a security hazard, as users' input should never be used to execute code in your programs. Second, since code doesn't write itself, the functions themselves have to be written and named, so the actual "variable named after function/function named after variable" scheme isn't much different than foo() if (/foo/).
    This pseudo-"functional programming" is better written as a hash with the variable name as key and a function ref as value, similar (but not limited) to this example:

    use strict; use warnings; my %hash = ( foo => sub {return "foo\n"}, bar => sub {return "bar\n"}, ); print &{$hash{'foo'}};

    Also, with single quotes, 'foo\n' prints foo\n rather than "foo" and a newline.

    Software speaks in tongues of man.
    Stop saying 'script'. Stop saying 'line-noise'.
    We have nothing to lose but our metaphors.

      I saw the root node's title and the number of replies and without even looking at the content figured people were demonstrating lots of different ways to do dispatch tables. Then actually reading the thread, I saw no dispatch tables at all, until the very last reply, by Erez.

      Better late than never :)

      If (as I guess from the OP's update) the subs do more than one-line of work, you can still use a dispatch table, but arranged like:

      sub foo { ... } sub bar { ... } my %hash = ( foo => \&foo, bar => \&bar, ); $var = "foo"; &{$hash{$var}}(@args);
Re: Invoke sub whose name is value of scalar
by ikegami (Patriarch) on Apr 14, 2008 at 13:02 UTC
    my $ref = do { no strict 'refs'; \&$var }; $ref->();

    There's usually better ways of achieving one's goals. We could help you better if we knew what you were actually trying to do.

      There's usually better ways of achieving one's goals. We could help you better if we knew what you were actually trying to do.

      I have used symbolic references to simulate linked lists. For example:

      sub _create_node { my ($node,$parent,$node_info) = @_; no strict; @{$node}{qw/parent info/} = ($parent,$node_info); }

      This way, each node is a hash with the name of its parent under the key "parent" and the info related to the node under the key "node_info". To go through a node:

      sub go_through { no strict 'refs'; my ($self, $node) = @_; return "" unless defined ${$node}{"info"}; my @info_nodes; while (${$node}{"info"} ne "root"){ push @info_nodes, (${$node}{"info"}); $node = ${$node}{"parent"}; } return \@info_nodes; }

      This runs very fast and doesn't need too much memory

      citromatik

        Except that's not a symbolic reference. That's a hash-reference and your code is more commonly written as:
        sub go_through { my ($self, $node) = @_; return unless defined $node->{info}; my @info_nodes; while ( $node->{info} ne 'root' ) { push @info_nodes, $node->{info}; $node = $node->{parent}; } return \@info_nodes; }
        This version also fixes a bug you had in your original code regarding contexts.

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Invoke sub whose name is value of scalar
by citromatik (Curate) on Apr 14, 2008 at 13:15 UTC

    What you are trying to do is called a symbolic reference and needs to be done without reference strict-ness, see perlref for more details

    use strict; use warnings; sub foo { print "foo\n"; } sub bar { print "bar\n"; } my $var = 'foo'; no strict 'refs'; &$var; ## <-

    Hope this helps

    citromatik

Re: Invoke sub whose name is value of scalar
by ikegami (Patriarch) on May 02, 2008 at 04:03 UTC

    [ You asked me to comment on your update. It was a while ago, so I hope this is still useful to you. ]

    This sounds like the perfect situation for a dispatch table.

    # Setup my %validators = ( is_int => \&is_int, is_string => \&is_string, is_yyyy_mm_dd_date => \&is_yyyy_mm_dd_date, ); # Load configuration my @validators; while (<$config_fh>) { # Or whatever my ($col, $validator_name) = /^col(\d+)=(\w+)$/ # or die; exists( $validators{$validator_name} ) or die; $validators[$col] = $validators{$validator_name}; } # Process data. my $csv = Text::CSV->new(sep_char => ';'); while (<$data_fh>) { $csv->parse($_) or die; my @fields = $csv->fields(); for my $i ( 0..$#validators ) { my $validator = $validators[$i]; next if !defined( $validator ); $validator->($fields[$i]) or die; } }
Re: Invoke sub whose name is value of scalar
by Anonymous Monk on Apr 15, 2008 at 15:00 UTC
    If I understand your updated question correctly, you are looking for an approach to the problem of associating the proper validation function to each column (or field) of a CSV record.

    One approach would be to build an array  @col_validators in which each element of the array holds a reference to the validation function for the corresponding column of the data record; e.g., from the example you have given in your question, element 1 of the array holds a reference to the  is_int() integer data validation function.

    This array would be built when the configuration file is parsed: as each config statement is parsed, the array is checked to see that a validation function has not been assigned to that column already, that the validation function exists, etc.; the function reference specified in the config statement is then assigned to the specified array element.

    After the array is built, it should be checked to see that no empty slots exist or that any empty slots are filled with a reference to a null or error-reporting function.

    When the array is used, it only remains the check that a column parsed from a record corresponds to an existing element in the validation array (if it does not, the record itself may be invalid), and you're done.

    BTW -- Since your updated question is differs significantly from the original, a re-post seems warranted.