Recipe 10.17 Writing a Switch Statement

10.17.1 Problem

You want to write a multiway branch statement, much as you can in C using its switch statement or in the shell using casebut Perl seems to support neither.

10.17.2 Solution

Use the Switch module, standard as of the v5.8 release of Perl.

use Switch;
switch ($value) {
    case 17         { print "number 17"       }
    case "snipe"    { print "a snipe"         }
    case /[a-f]+/i  { print "pattern matched" }
    case [1..10,42] { print "in the list"     }
    case (@array)   { print "in the array"    }
    case (%hash)    { print "in the hash"     }
    else            { print "no case applies" }

10.17.3 Discussion

The Switch module extends Perl's basic syntax by providing a powerful and flexible switch construct. In fact, it's so powerful and flexible that instead of a complete description of how it works, we'll instead provide examples of some common uses. For the full story, make sure to consult the documentation that accompanies the module.

A switch takes an argument and a mandatory block, within which can occur any number of cases. Each of those cases also takes an argument and a mandatory block. The arguments to each case can vary in type, allowing (among many other things) any or all of string, numeric, or regex comparisons against the switch's value. When the case is an array or hash (or reference to the same), the case matches if the switch value corresponds to any of the array elements or hash keys. If no case matches, a trailing else block will be executed.

Unlike certain languages' multiway branching constructs, here once a valid case is found and its block executed, control transfers out of the enclosing switch. In other words, there's no implied fall-through behavior the way there is in C. This is considered desirable because even the best of programmers will occasionally forget about fall-through.

However, this is Perl, so you can have your cake and eat it, too. Just use a next from within a switch to transfer control to the next case. Consider:

%traits = (pride => 2, sloth => 3, hope => 14);
switch (%traits) {
    case "impatience"                      { print "Hurry up!\n";       next }
    case ["laziness","sloth"]              { print "Maybe tomorrow!\n"; next }
    case ["hubris","pride"]                { print "Mine's best!\n";    next }
    case ["greed","cupidity","avarice"]    { print "More more more!";   next }

Maybe tomorrow!
Mine's best!

Because each case has a next, it doesn't just do the first one it finds, but goes on for further tests. The next can be conditional, too, allowing for conditional fall through.

You might have noticed something else interesting about that previous example: the argument to the switch wasn't a scalar; it was the %traits hash. It turns out that you can switch on other things than scalars. In fact, both case and switch accept nearly any kind of argument. The behavior varies depending on the particular combination. Here, the strings from each of those cases are taken as keys to index into the hash we're switching on.

If you find yourself preferring fall-through as the default, you can have that, too:

use Switch 'fallthrough';
%traits = (pride => 2, sloth => 3, hope => 14);
switch (%traits) {
    case "impatience"                      { print "Hurry up!\n"        }
    case ["laziness","sloth"]              { print "Maybe tomorrow!\n"  }
    case ["hubris","pride"]                { print "Mine's best!\n"     }
    case ["greed","cupidity","avarice"]    { print "More more more!"    }

One area where a bunch of cascading ifs would still seem to excel is when each test involves a different expression, and those expressions are more complex than a simple string, numeric, or pattern comparison. For example:

if    ($n % 2 =  = 0) { print "two "   }
elsif ($n % 3 =  = 0) { print "three " }
elsif ($n % 5 =  = 0) { print "five "  }
elsif ($n % 7 =  = 0) { print "seven " }

Or if you want more than one test to be able to apply, you can do this with fall-through behavior:

if ($n % 2 =  = 0) { print "two "   }
if ($n % 3 =  = 0) { print "three " }
if ($n % 5 =  = 0) { print "five "  }
if ($n % 7 =  = 0) { print "seven " }

Perl's switch can handle this too, but you have to be a bit more careful. For a case item to be an arbitrary expression, wrap that expression in a subroutine. That subroutine is called with the switch argument as the subroutine's argument. If the subroutine returns a true value, then the case is satisfied.

use Switch 'fallthrough';
$n = 30;
print "Factors of $n include: ";
switch ($n) {
    case sub{$_[0] % 2 =  = 0} { print "two "   }
    case sub{$_[0] % 3 =  = 0} { print "three " }
    case sub{$_[0] % 5 =  = 0} { print "five "  }
    case sub{$_[0] % 7 =  = 0} { print "seven " }

That's pretty cumbersome to writeand to readbut with a little bit of highly magical syntactic sugar, even that clumsiness goes away. If you import the _ _ subroutine (yes, that really is a double underscore), you can use that in an expression as the case target, and the _ _ will represent the value being switched on. For example:

use Switch qw( _ _  fallthrough );
$n = 30;
print "Factors of $n include: ";
switch ($n) {
    case _ _ % 2 =  = 0 { print "two "     }
    case _ _ % 3 =  = 0 { print "three "   }
    case _ _ % 5 =  = 0 { print "five "    }
    case _ _ % 7 =  = 0 { print "seven "   }
print "\n";

Due to the way that _ _ is implemented, some restrictions on its use apply. The main one is that your expression can't use && or || in it.

Here's one final trick with switch. This time, instead of having a scalar in the switch and subroutines in the cases, let's do it the other way around. You can switch on a subroutine reference; each case value will be passed into that subroutine, and if the sub returns a true value, then the case is deemed to have matched and its code block executed. That makes the factor example read:

use Switch qw(fallthrough);
$n = 30;
print "Factors of $n include: ";
switch (sub {$n % $_[0] =  = 0} ) {
    case 2 { print "two "   }
    case 3 { print "three " }
    case 5 { print "five "  }
    case 7 { print "seven " }

This is probably the most aesthetically pleasing way of writing it, since there's no longer duplicate code on each line.

The Switch module uses a facility called source filters to emulate behavior anticipated in Perl6 (whenever that might be). This has been known to cause mysterious compilation errors if you use constructs in your code you were warned against. You should therefore pay very close attention to the section on "Dependencies, Bugs, and Limitations" in the Switch manpage.

10.17.4 See Also

The documentation for the Switch module; the perlsyn(1) manpage's section on "Basic BLOCKs and Switch Statements"; the section on "Case Statements" in Chapter 4 of Programming Perl