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

I know this has almost been covered in other questions, but not quite.

Here's the scenario. I have a perl program that runs a large LED screen. The program has an "idle loop", where I want it to choose a random function from a list of internal functions.

Ideally, I'd define an array with the names of the routines that can be used, build a hash by name of their references, and call one at random.

Of course I use 'strict' and 'warnings'. I'm willing to disable 'strict' locally for this one routine to build the hash. I am not worried about external subversion calling unintentional code as it's governed by an array that's hard-coded in the script.

Here's a sample of what I'm trying, but can't seem to get it to work. Anyone able to help?

use strict; + use warnings; + use Data::Dumper; + my @FuncList = qw(TestSub1 TestSub2); my %JumpTbl; foreach (@FuncList) { print "\$_=$_\n"; no strict 'refs'; $JumpTbl{$_} = \$_; } print Dumper(%JumpTbl); $JumpTbl{'TestSub2'}(); exit; sub TestSub1 { print "TestSub1\n"; } sub TestSub2 { print "TestSub2\n"; }

Output:

$_=TestSub1 $_=TestSub2 $VAR1 = 'TestSub1'; $VAR2 = \'TestSub1'; $VAR3 = 'TestSub2'; $VAR4 = \'TestSub2'; Not a CODE reference at ./triv.pl line 22.

Replies are listed 'Best First'.
Re: Calling func named in string
by choroba (Cardinal) on Dec 15, 2024 at 19:48 UTC
    For this, you don't even need to turn strict off (search the documentation for &):
    for my $funcname (@FuncList) { $JumpTbl{$funcname} = \&$funcname; }
    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

      Ooh, aah! Thank you for pointing this out!

      By changing to use the backslash-ampersand, I do get references. I would have sworn I'd tried this, but that it didn't work, I guess not. Anyways, here's what I've got now...

      use strict; + use warnings; my @FuncList = qw(TestSub1 TestSub2); my @CallFunc = map { \&$_ } @FuncList; my %JumpTbl = map { $_ => \&$_ } @FuncList; $CallFunc[1](); $JumpTbl{'TestSub1'}(); exit; sub TestSub1 { print "TestSub1\n"; } sub TestSub2 { print "TestSub2\n"; }

      Output:

      TestSub2 TestSub1
Re: Calling func named in string
by tybalt89 (Monsignor) on Dec 15, 2024 at 23:18 UTC
    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11163188 use warnings; use List::AllUtils qw( sample ); my @FuncList = qw(TestSub1 TestSub2); for ( 1 .. 10 ) { my $func = sample 1, @FuncList; main->$func; } sub TestSub1 { print "TestSub1\n"; } sub TestSub2 { print "TestSub2\n"; }

    Outputs:

    TestSub2 TestSub2 TestSub1 TestSub2 TestSub2 TestSub2 TestSub1 TestSub2 TestSub1 TestSub1

      Or as one-liner:

      #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11163188 use warnings; use List::AllUtils qw( sample ); my @FuncList = qw(TestSub1 TestSub2); for ( 1 .. 3 ) { main->${\sample 1, @FuncList}; } sub TestSub1 { print "TestSub1\n"; } sub TestSub2 { print "TestSub2\n"; }

      Outputs:

      TestSub2 TestSub1 TestSub2
Re: Calling func named in string
by tybalt89 (Monsignor) on Dec 18, 2024 at 03:21 UTC

    TIMTOWTDI

    Just stick your internal functions in their own package and use the package hash :)

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11163188 use warnings; use List::AllUtils qw( sample ); (sample 1, values %INTERNALFUNCTIONS::)->() for 1 .. 5; package INTERNALFUNCTIONS; sub TestSub1 { print "TestSub1\n"; } sub TestSub2 { print "TestSub2\n"; }

    Outputs:

    TestSub1 TestSub2 TestSub2 TestSub1 TestSub1

    Still strict, no fancy manipulations required, and you can dynamically add more functions :)
    Perl sure has some interesting tricks up its sleeve.

      Yes but be careful not define any other package vars in INTERNALFUNCTIONS::

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

Re: Calling func named in string
by MikeL (Acolyte) on Dec 15, 2024 at 19:50 UTC

    I'm answering my own question, I got it figured out - I was making this much harder than necessary. In the following, I included line 16 which calls outside of the "no strict 'refs'", causing an error, so as to show that "strict refs" is operating normally outside of the block. Also, if you only want to call out a sub by a number, you don't need the JumpTbl hash, that's only needed to call it out by name:

    use strict; + use warnings; my @FuncList = qw(TestSub1 TestSub2); my %JumpTbl = map { $_ => $_ } @FuncList; { no strict 'refs'; $JumpTbl{'TestSub2'}(); $FuncList[0](); } $JumpTbl{'TestSub1'}(); exit; sub TestSub1 { print "TestSub1\n"; } sub TestSub2 { print "TestSub2\n"; }

    Output:

    TestSub2 TestSub1 Can't use string ("TestSub1") as a subroutine ref while "strict refs" +in use at ./triv.pl line 16.

      Strictly speaking, if you get the syntax right there is no need for no strict 'refs';. See choroba's reply above and your updated test code below.

      use strict; use warnings; my %JumpTbl = (TestSub1 => \&TestSub1, TestSub2 => \&TestSub2); my @FuncList = values %JumpTbl; $JumpTbl{'TestSub2'}(); $FuncList[0](); sub TestSub1 { print "TestSub1\n"; } sub TestSub2 { print "TestSub2\n"; }
      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

        There is a difference here...

        My goal was to type the list of names once, as it's fairly long (about 50). This is for both ease of typing, and for clarity of code.

        Your solution requires the redundant "thing => thing", whereas what I arrived at is simply a list of "things".

        I agree that it's a Good Thing to not turn off strict, but I'm willing to accept that in a very localized area, for this one line of code, in order to make this happen.

        P.S. Better solution arrived at above https://www.perlmonks.org/?node_id=11163205