http://qs321.pair.com?node_id=34223

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

Suppose I have a hash whose values are anonymous functions, e.g.,
%functions = ( '?' => 0, 'HELLO' => sub { print "Hello, world.\n" }, 'HELP' => sub { print "Type 'hello' or 'goodbye'\n" }, 'GOODBYE' => sub { print "Goodbye...\n"; exit(0) } ); # have to initialize this outside declaration of %functions $functions{'?'} = $functions{'HELP'};
Then, suppose I want to take input from the user and see if the things the user types are keys in the hash. If so, I execute the associated function.
while(<>) { $func = $_; chomp($func); $func = uc($func); if( defined($functions{$func}) ) { ### insert code here to execute function in hash ### } else { print "\"$func\" not defined\n"; } }
My question is this. At the spot to insert the code to execute the function, I would like to do this:
&$functions{$func};
but perl won't let me. I have found that if I do this:
my $code; $code = $functions{$func}; &$code;
it works. Why doesn't the first work?

Replies are listed 'Best First'.
Re: using a hash of functions
by merlyn (Sage) on Sep 27, 2000 at 19:52 UTC
    The canonical form of a subroutine invocation is:
    $result = & { something_that_returns_coderef } ( @args );
    The braces around the coderef expression can be dropped only when the coderef is in a simple scalar variable. Yours was not. So you'd have to use:
    & { $functions{$func} }();
    However, as of fairly recent Perls, you can also use an arrow form:
    $functions{$func}->();
    This was added by Chip in 5.4 under some urging by me based on a bet I made that I couldn't get a new feature into 5.4 during the gamma golden release phase. I won. {grin}

    -- Randal L. Schwartz, Perl hacker


    update: for more details, check out my Linux magazine column on subroutine references, and an older column from UnixReview.
multiple keys - one value
by japhy (Canon) on Sep 27, 2000 at 21:44 UTC
    Wouldn't it be nice if there was some idiom (say, a magical hash...) that would allow me to say:
    %hash = ( HELP => sub { ... }, '?' => $_{HELP}, QUIT => sub { ... }, EXIT => $_{QUIT}, LEAVE => $_{QUIT}, );
    Instead of
    %hash = ( HELP => sub { ... }, QUIT => sub { ... }, ); $hash{'?'} = $hash{HELP}; $hash{EXIT} = $hash{LEAVE} = $hash{QUIT};


    $_="goto+F.print+chop;\n=yhpaj";F1:eval
        Thank you, Randal. That looks about right, and perfectly Perlish.

        $_="goto+F.print+chop;\n=yhpaj";F1:eval
        I know I'm only 3 years late, but could someone explain/dissect in teaching terms, just what merlyn did. /msg me if you wouldn't mind so I know it's up. Thanks.

        —Brad
        "A little yeast leavens the whole dough."
      This is a little clumsy, but you could have  '?' => 'HELP', etc, then use a loop at startup that replaced non-code refs with the code ref of the hash indice the value side points to.

      It doesn't completely solve the semantic problem, but if you rename a routine, you only have to touch it one place, and adding synonyms becomes trivial.
      #!/usr/local/bin/perl -w use strict; my %hash = ('HELP' => \&do_help, '?' => 'HELP', 'LIST' => \&do_list, 'SHOW' => 'LIST', 'TEST' => \&do_test, ); { while (my ($k, $v) = each (%hash)) { $hash {$k} = $hash {$v} if (ref $hash {$k} ne 'CODE'); } &{$hash {'HELP'}}; &{$hash {'?'}}; &{$hash {'LIST'}}; &{$hash {'SHOW'}}; &{$hash {'TEST'}}; } sub do_help {print "help\n"} sub do_list {print "list\n"} sub do_test {print "test\n"}
      --Chris

      e-mail jcwren
      If you're only duplicating one value, you can use
      my %h = ( HELP => $_ = sub {print "help\n"}, "?" => $_, SAVE => sub {print "save\n"}, );
      You have to use one variable per value though:
      my ($help, $save); my %h = ( HELP => $help = sub {print "help\n"}, "?" => $help, SAVE => $save = sub {print "save\n"}, EXIT => $save, );
        I'm trying to improve on sandfly's code by eliminating the need for temp variables, but getting into trouble. Why does the following code produce
        "Use of uninitialized value in subroutine entry at -e line 1.
        Undefined subroutine &main:: called at -e line 1."
        my %h=( HELP => sub {print qq(help\n)}, Q=>$h{HELP}, SAVE=>sub{print qq(save\n)} ); print $h{Q}();
        I have also tried it by separating the declaration and population of %h, to no avail.
        OK I answered my own question based on one of my old writeups. It IS possible to eliminate the temporary variable by coding as follows:
        use strict; my %h; #Pre-declare so it can be referenced inside populating code %h=( HELP => sub {print qq(help\n)}, '?'=>sub{$h{HELP}()}, SAVE=>sub{print qq(save\n)}, EXIT=>sub{$h{SAVE}()} ); $h{'?'}(); $h{EXIT}();
        This prints the lines:
        help
        save
Re: using a hash of functions
by swiftone (Curate) on Sep 27, 2000 at 19:50 UTC
    My guess would be that the perl parser is getting a bit confused. Have you tried: &{$functions{$func}} ?

    Update: merlyn explains "How to tell Perl What You Mean" below.

RE: using a hash of functions
by runrig (Abbot) on Sep 27, 2000 at 20:39 UTC
    And to be more perlish, I'd probably do it this way:
    while(<>) { chomp; my $func = uc($_); print (qq!"$func" not defined\n!), next unless exists $functions{$fu +nc}; $functions{func}->(); }