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

Give this code, would you keep it or change it? How?
local $_ = $value; my $foo = (/aaa/) ? 'wanna' : (/bbb/) ? 'be' : (/ccc/) ? 'startin' : 'somethin';
I was thinking of using a switch instead, but didn't really see a reason to... What do you think?

Replies are listed 'Best First'.
Re: Would you use a switch instead?
by Corion (Patriarch) on Jul 04, 2009 at 13:08 UTC

    Perl has no switch statement. Maybe you mean a dispatch table, which I'd consider using, or a given statement, if you're using Perl 5.10 or higher.

      Perl has no switch statement

      There is the switch module or given/when, etc. Plus, you can mimic a switch with if/else's (although I never liked doing so).

Re: Would you use a switch instead?
by FunkyMonk (Chancellor) on Jul 04, 2009 at 13:34 UTC
    There is the Switch module, but it's a source filter and considered evil. Perl 5.10 has the given/when statements, that are the equivalent of Switch.

    If you're stuck on an earlier perl, you could also use something like:

    for ($value) { /aaa/ && do { $foo = 'wanna'; last }; /bbb/ && do { $foo = 'be'; last }; /ccc/ && do { $foo = 'startin'; last }; $foo = 'somethin'; }

    Tested with dodgy eyes only!

      And, of course, foreach-loop topicalization can also be combined with the original nested ternary expression, although definition and initialization of the variable  $foo are no longer so closely tied together:
      >perl -wMstrict -le "my $value = 'bbb'; my $foo; for ($value) { $foo = /aaa/ ? 'wanna' : /bbb/ ? 'be' : /ccc/ ? 'startin' : 'somethin' ; } print $foo; " be

        but of course we always use an explicit lexical as a loop variable so there's no gain there is there? ;)


        True laziness is hard work
Re: Would you use a switch instead?
by eyepopslikeamosquito (Archbishop) on Jul 05, 2009 at 04:50 UTC

    See Perl Best Practices: items 6.15 (avoid cascading an if), 6.16 (use table look-up in preference to cascaded equality tests), and 6.17 (when producing a value, use tabular ternaries). I'd say PBP would endorse your original "tabular ternary" solution, albeit with a different layout style. PBP (item 6.17) compares:

    my $salute; if ($name eq $EMPTY_STR) { $salute = 'Dear Customer'; } elsif ($name =~ m/\A ((?:Sir|Dame) \s+ \S+)/xms) { $salute = "Dear $1"; } elsif ($name =~ m/([^\n]*), \s+ Ph[.]?D \z/xms) { $sa1ute = "Dear Dr $1"; } else { $salute = "Dear $name"; }
    with:
    # Name format... # Salutat +ion... my $salute = $name eq $EMPTY_STR ? 'Dear Cus +tomer' : $name =~ m/ \A((?:Sir|Dame) \s+ \S+) /xms ? "Dear $1" : $name =~ m/ (.*), \s+ Ph[.]?D \z /xms ? "Dear Dr +$1" : "Dear $na +me" ;
    and endorses the latter because:
    • it avoids the (error-prone) repeated assignment to $salute
    • it is more compact
    • it looks like a table (if laid out as above)
    • The syntax is stricter; for example, with the tabular ternary you can't accidentally leave off the final else clause, a mistake that might be made in the original version

Re: Would you use a switch instead?
by Arunbear (Prior) on Jul 04, 2009 at 21:02 UTC
    Whether to keep it or change it depends on what you find more readable. I find the switch feature more readable than nested ternaries:
    use strict; use warnings; use feature qw( switch say ); sub test_it { my $value = shift; given($value) { when(/aaa/) { return 'wanna' } when(/bbb/) { return 'be' } when(/ccc/) { return 'startin' } default { return 'somethin' } } } foreach my $str (qw[testaaa testbbb testccc testddd]) { say test_it($str); }
    Update
    I tried to remove the returns by wrapping the given in a do, but it seems the default/when blocks don't return anything to the do in the way that if/else blocks do:

      Thanks for encapsulating the code in question into a subroutine. I always felt that this makes for easier-to-read high-level code, and permits modifying how the underlying test is done with minimal high-level changes.
Re: Would you use a switch instead?
by JavaFan (Canon) on Jul 04, 2009 at 13:26 UTC
    I'd leave it as is. Even if I would not have written it that way (I'd leave off the parens, and if I wanted to localize $_, I'd use a do {} block), I don't see the benefit of putting resources in changing it.
      Can you please show me how you would use a do{} block instead of local?
        my $foo = do { local $_ = $value; /aaa/ ? 'wanna' : /bbb/ ? 'be' : /ccc/ ? 'startin' : 'somethin' };
Re: Would you use a switch instead?
by Marshall (Canon) on Jul 06, 2009 at 11:46 UTC
    I personally wouldn't do it this way:
    ### local $_ = $value; my $foo = (/aaa/) ? 'wanna' : (/bbb/) ? 'be' : (/ccc/) ? 'startin' : 'somethin'; ###
    Point 1: "local" is a "rare duck" - it happens seldom. Almost always "my" is better. local is an artifact of previous Perl versions, but it still has uses. Point 2: I figure that setting $_ as an lvalue is a bad idea. That is motivated the fact that Perl can and will do this by itself. Point 3: I have no problem with ternary expressions. But I I do have a problem with an ":" without a "blah;" Point 4: If we have a complex decision with 4 values, probably we can come up with a name for that decision and I'd name a sub{} with that name. Point 5: If the thing we are searching is small and performance is not an issue, I would omit "last" and "next" in the interest of simplicity. So, this is one way to do this.. my $foo = decide($value); sub decide { my $value = shift; my $decision = 'somethin'; foreach ($value) #sets $_ to $value! { $decision = 'wanna' if (/aaa/); $decision = 'be' if (/bbb/); $decision = 'startin' if (/ccc/); } return ($decision ); }
    Perl is a fantastic tool. The above is just one way to approach this problem.

    Update:In many languages, the return value of decide() would be true/false. Perl can return a string from a sub and this enhances the readability.

    if $foo eq 'be'{...do xxxx...}