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

Why won't strict let me replace this redundant drek
$in=param('in'); if($in eq "this"){&this} if($in eq "that"){&that} if($in eq "foo"){&foo} if($in eq "bar"){&bar} if($in eq "zzz"){&zzz} # imagine a lot more
with this?
$in=param('in'); @actions=qw(this that foo bar zzz); # names of subroutines and input v +alues foreach $_(@actions){ if($in eq $_){&$_()}}
Can't use string ("foo") as a subroutine ref while "strict refs" in use.

Is there a strict way to do something similar?

getting there - epoptai

ref: h0mee

Replies are listed 'Best First'.
Re: use slack;
by chromatic (Archbishop) on Dec 19, 2000 at 08:46 UTC
    Because you're still doing the same thing -- using a string as the name of a function.

    Here's the hash of subroutine references:

    my %actions = ( this => \&this, that => \&that, foo => \&foo, bar => \&bar, zzz => \&sleepytime, ); # ooh, tricky foreach (@user_actions) { if (defined(my $sub_ref = $actions{$_})) { $sub_ref->(); } }
      Since the original intent appeared to be to run just one subroutine, you can probably even lose the subroutine in chromatics example:
      my $in = param('in'); my %actions = ( this => \&this, that => \&that, foo => \&foo, bar => \&bar, zzz => \&sleepytime, ); defined(my $sub_ref = $actions{$in}) && $sub_ref->() ;
      -mark
      With all due respect to those suggesting the hash, both here and in the chatterbox, saints even. I have 2 problems with the hash:
      my %actions = ( this => \&this, that => \&that, foo => \&foo, bar => \&bar, zzz => \&sleepytime);
      1. it resembles the original redundant code that was eliminated:
      if($in eq "this"){&this} if($in eq "that"){&that} if($in eq "foo"){&foo} if($in eq "bar"){&bar} if($in eq "zzz"){&zzz}
      2. my feeble attempts to implement it do not even succeed, (very possibly due to my own implementation bugs).

      The hash plus every variant of code (supplied in replies) to access it gives me:

      with use strict: Can't use string ("\&foo") as a subroutine ref while "strict refs" in use

      with no strict "refs"; Undefined subroutine main::\&foo

      I really want to collapse the kind of repetition represented in my original code, because i'm adding so many options to some scripts that all the ifs (or the hash) are getting ridiculous. I can replace any number of lines related to testing input with ONE:

      foreach(@actions){ if($in eq $_){&$_()}}
      I did resort to no strict "refs"; and thanks to alakaboo moved that line inside the foreach loop from the top near use strict;.
      $in=param('in'); @actions=qw(this that foo bar zzz); foreach(@actions){ no strict "refs"; if($in eq $_){&$_()}}
      I also like using the array like this because it was already there being used for other purposes! How can this be unsafe in practice? The sub name $_ comes from @actions which is predefined and only happens if input matches it. I've tried feeding it other sub names that exist in the script, but it only executes ones that are defined in the array.
        Says epoptai:
        > Can't use string ("\&foo")
        That's because your hash has
        that => '\&that',
        when it should have
        that => \&that,
(no strict) Re: use slack;
by mwp (Hermit) on Dec 19, 2000 at 11:58 UTC
    You could cheat and do this:
    my $in = $cgi->param('in'); my @actions = qw(this that foo bar zzz); { no strict 'refs'; foreach(@actions) { $_->() if($_ eq $in); } }

    But I like chromatic's suggestion better.

    'kaboo

Re (tilly) 1: use slack;
by tilly (Archbishop) on Dec 19, 2000 at 23:38 UTC
    You do have code for these cases? So in addition to your logic above you have:
    sub this { return "this"; # Whatever } sub that { return "that"; # Being simple here } # etc
    Combine the two:
    my %action = ( this => sub {return "this"}, that => sub {return "that"}, # etc ); # Then later: if (exists $action{$in}) { $action{in}->(); }
    In fact if you have a lot of cases you can probably find room to factor:
    sub ret_simple_returner { my $arg = shift; return ($arg, sub {$arg}); } my %action = map ret_simple_returner($_), qw(this that);
    The point being that every time you eliminate the need to keep two pieces of code in sync you eliminate another possible source of bugs...
Re: use slack;
by Dominus (Parson) on Dec 20, 2000 at 02:45 UTC
    > Is there a strict way to do something similar?
    Sure.

    foreach $_(@actions){ if($in eq $_){no strict 'refs'; &$_()}}
    There you go.

Re: use slack;
by autark (Friar) on Dec 20, 2000 at 19:18 UTC
    I hate to turn off strict. Why use strict if you are only going to turn it off ?

    I usually do this:

    use strict; sub foo { print "bar\n" } my $func = "foo"; my $func_ref = \&{$func}; $func_ref->();
    The reason this works is because the construct \&{$func} is exempt from the rule of strict references. It is as far as I know not documented in earlier perls, but code support for this construct has been in for a long time. It is documented in bleadperl IIRC

    The construct is often used in AUTLOAD like this:

    sub AUTOLOAD { # get params and function # ... my $func_ref = \&{$func}; goto &{$func_ref}; }
    Autark.
      Says autark:
      > I hate to turn off strict. Why use strict if you are only going to turn it off ?
      Strict is a safety feature. It is there to prevent you from accidentally doing something dangerous. Like all safety features, it also prevents some things that are not really dangerous. For example, sometimes my kitchen smoke alarm goes off when I am cooking onions.

      If what you want is to do something that is not dangerous, but which the alarm is too stupid to distinguish from dangerous behavior, it is entirely appropriate to turn off the alarm. Should I stop cooking onions, out of some superstitious fear that maybe the alarm knows better than I do? No, that would be ridiculous.

      You asked why have the alarm, if I am just going to shut it off? That is the wrong question. I am not going to shut it off all the time; just when it interferes with my cooking of onions. The rest of the time, it will be available to perform its function, which is to warn me about accidental fires.

      The alarm is a means to an end. The goal is to prevent fires. Keeping the alarm quiet is not a goal in itself; it is only a way to help prevent fires. Keeping strict quiet is not a goal in itself, either. The goal is to avoid the dangerous and difficult bugs that can result from careless use of symbolic references. No dangerous problems can occur in this case.

      But apparently a lot of people in this thread feel the way you do, because we've seen a whole bunch of clumsy, overcoded solutions, all just to get around turning off the smoke alarm, when what's really wanted is just to turn off strict for one line and cook the damn onions already.

        I agree that in the AUTOLOAD example it is probably best to just turn off strict rather than to use a weird workaround.

        In the example that he started with, I prefer looking up the correct function in a hash to scanning a list for it. YMMV. But the two wins I see are that you don't have to synchronize the name of the function with the name of the element in the list, and you don't get cow-orkers tempted to drop the scan and just jump to the function if it exists. (Which takes you from a safe use of symbolic references to a potentially very dangerous one.)

        Also the conceptual step of knowing that you can keep functions in a hash is (in my eyes) a worthwhile step forward in sophistication.

      Thanks for the occult info autark! Please correct me if i'm wrong but as far as i can tell saying no strict "refs" inside a tight loop only turns strict refs off for the duration of the loop.

      Aside from this one exception employed for a well-tested condition (for a huge optimization):

      if($in){ foreach(@actions){ unless($_ eq /one|/){ if($in eq $_){ no strict "refs"; &$_()}}}}
      the (now 400 line) script uses strict. Since i know how to play by the rules why not also start to break them, since it seems to pay off so well when done carefully?

      UPDATE: I responded too quickly, as autark explained in the chatterbox, this is the perfect solution. I get to do what I want without breaking strict with:

      if($in){ foreach(@actions){ unless($_ eq /one|/){ if($in eq $_){ do{\&{$_}}->()}}}}
      Joy!
        The strict pragma is lexically scoped, so it will only affect the innermost block of your program.

        Now, there is no rule saying that you have to use strict;, it's only good advice :-). Though, I'm a bit curious how breaking these "rules" have paid off ?

        Autark.

Re: use slack;
by steveAZ98 (Monk) on Dec 20, 2000 at 20:03 UTC
    How about changing to a object system. This might releive some of your redundancy?
    package myObj; use strict; sub new { return bless {}, $_[0]; } sub one { print "One\n"; } sub two { print "Two\n"; } sub three { print "Three\n"; } 1;
    and test code:
    #!/usr/bin/perl -w use strict; use lib "."; use myObj; my $s = myObj->new(); my @cmd = qw( one two three ); foreach (@cmd) { $s->$_(); }
    HTH
      Very interesting steveAZ98. I can see that this technique will be useful for future projects. Thanks for adding more light. I've uploaded the entire script that i was working on when this question came up here. It was inspired by the replies to this question. Thanks PM!