Writing a Switch Statement (Perl Cookbook, 2nd Edition)

## 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 `case`—but 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 `case` s. Each of those `case` s 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 `case` s are taken as keys to index into the hash we're `switch` ing 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 write—and to read—but 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 `switch` ed 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 `case` s, 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.

> WARNING:

> 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

Copyright © 2003 O'Reilly & Associates. All rights reserved.