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
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 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 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
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
|