Obtain a switch/case behaviour in Perl 5
Asked Answered
C

5

29

Is there a neat way of making a case or switch statement in Perl 5?. It seems to me they should include a switch on version 6.

I need this control structure in a script, and I've heard you can import a "switch module". But how can I achieve it without imports to minimize dependencies and acquire portability?

Contorted answered 10/5, 2009 at 3:15 Comment(2)
The Switch module is part of Core Perl, and has been since 5.8, so there is no dependency issue; however, it does have some bugs and limitations. See the docs for more info, or better yet, upgrade to 5.10 and get a switch statement, smart match operator, say function, state variables, a host of regex improvements, defined-or (//), stacked file test operators (-r -x $file instead of -r $file && -x _), and a host of other improvements.Blaseio
See this perlfaq7 question. (Don't use given/when or the Switch module.)Ozenfant
B
59

If you are using Perl 5.10 you have given/when which is a switch statement (note, it can do more than compare with regexes, read the linked docs to see its full potential):

#or any of the dozen other ways to tell 5.10 to use its new features
use feature qw/switch/; 

given($string) {
    when (/^abc/) { $abc     = 1; }
    when (/^def/) { $def     = 1; }
    when (/^xyz/) { $xyz     = 1; }
    default       { $nothing = 1; }
}

If you are using Perl 5.8 or earlier you must make do with if/elsif/else statements:

if    ($string =~ /^abc/) { $abc     = 1; }
elsif ($string =~ /^def/) { $def     = 1; }
elsif ($string =~ /^zyz/) { $xyz     = 1; }
else                      { $nothing = 1; }

or nested condition operators (?:):

$string =~ /^abc/ ? $abc     = 1  :
$string =~ /^def/ ? $def     = 1  :
$string =~ /^xyz/ ? $xyz     = 1  :
                    $nothing = 1;

There is a module in Core Perl (Switch) that gives you fake switch statements via source filters, but it is my understanding that it is fragile:

use Switch;

switch ($string) {
    case /^abc/ {
    case /^abc/ { $abc     = 1 }
    case /^def/ { $def     = 1 }
    case /^xyz/ { $xyz     = 1 } 
    else        { $nothing = 1 }
}

or the alternate syntax

use Switch 'Perl6';

given ($string) {  
    when /^abc/ { $abc     = 1; }
    when /^def/ { $def     = 1; }
    when /^xyz/ { $xyz     = 1; }
    default     { $nothing = 1; }
}
Blaseio answered 10/5, 2009 at 3:37 Comment(4)
The Perl 5.10 version sets $_ like it does in Perl 6. the Switch module's given/when doesn't.Majolica
'when' is an implicit '~~' operator, so not only regexes are allowed.Impasse
given/when is an experimental feature that's been deemed a failure. It shouldn't used as it can be removed/changed without warning. /// The Switch module uses source filters and do their own parsing of the Perl code. This leads to very weird error messages that are basically impossible to make sense of. It should be avoided as well.Ozenfant
I concur that given/when is evil. It creates nasty bugs in a code that works just fine under another Perl build (using v5.10.1 in both environments). I came here looking for the official alternative, if it exists.Tameka
V
20

The suggestion in Programming Perl is:


for ($string) {
    /abc/ and do {$abc    = 1; last;};
    /def/ and do {$def    = 1; last;};
    /xyz/ and do {$xyz    = 1; last;};
    $nothing = 1;
}
Vishinsky answered 10/5, 2009 at 3:59 Comment(3)
From the docs: "do BLOCK does not count as a loop, so the loop control statements next, last, or redo cannot be used to leave or restart the block." perldoc.perl.org/functions/do.html So last seems at least unnecessary?Ret
the do block isn't part of a loop, but the last refers to the for loop, not to the do.Vishinsky
This still has the dangers of given/when, without the warning. See https://mcmap.net/q/501204/-hidden-bugs-with-given-when-and-for-match-is-perl-truly-cross-platform/3092394Tameka
O
11

Just a short comment about the core Switch module that's been mentioned a couple of times in answers. The module in question relies on source filters. Among other things, that may result in wrong lines reported for errors. It's so bad that none of the core developers really remembers or cares to remember why it was accepted into the perl core in the first place.

Furthermore, Switch.pm will be the first Perl module ever to be removed from the perl core. The next major release of perl, 5.12.0, will still have it, albeit with a deprecation warning. That deprecation warning will go away if you explicitly install Switch.pm from CPAN. (You get what you ask for.) In the next release down the road, 5.14, Switch.pm will be entirely removed from core.

Outpouring answered 12/5, 2009 at 7:23 Comment(0)
W
9

An equivalent solution that I like is a dispatch table.

my $switch = {
  'case1' => sub { print "case1"; },
  'case2' => sub { print "case2"; },
  'default' => sub { print "unrecognized"; }
};
$switch->{$case} ? $switch->{$case}->() : $switch->{'default'}->();
Woundwort answered 11/5, 2009 at 15:36 Comment(3)
Shorter: ($switch->{$case} || $switch->{default})->()Ranie
This is beautiful!Vasectomy
concatenating variables for $case makes this very efficient for complex logicRemus
P
1
print("OK : 1 - CANCEL : 2\n");
my $value = <STDIN>;
SWITCH: {
    ($value == 1) && last(SWITCH);
    ($value == 2) && do {print("Cancelled\n"); exit()};
    print("??\n");
}
Pinkney answered 7/8, 2013 at 20:1 Comment(1)
Please consider add some description of your answer.Toxicant

© 2022 - 2024 — McMap. All rights reserved.