[% setvar title Control flow: Builtin switch statement %]
Note: these documents may be out of date. Do not use as reference! |
To see what is currently happening visit http://www.perl6.org/
Control flow: Builtin switch statement
Maintainer: Damian Conway <damian@conway.org> Date: 4 Aug 2000 Last Modified: 18 Sep 2000 Mailing List: perl6-language@perl.org Number: 22 Version: 2 Status: Frozen
This RFC proposes a syntax and semantics for an explicit case mechanism for
Perl. The syntax is minimal, introducing only the keywords switch
and
case
and conforming to the general pattern of existing Perl control
structures. The semantics are particularly rich, allowing any one (or
more) of nearly 30 forms of matching to be used when comparing a switch
value with its various cases.
In seeking to devise a "Swiss Army" case mechanism suitable for Perl, it is useful to generalize the switch/case notion of distributed conditional testing as far as possible. Specifically, the concept of "matching" between the switch value and the various case values need not be restricted to numeric (or string or referential) equality, as it is in other languages. Indeed, as Table 1 illustrates, Perl offers at least seventeen different ways in which two values could generate a match.
$s
) with a case value (<$c>)Switch Case Type of Match Implied Matching Code Value Value ====== ===== ===================== ============= number I<same> numeric or referential C<match if $s == $c;> or ref equality object string result of method call <match if $s->$c();> ref or sub ref other other string equality C<match if $s eq $c;> non-ref non-ref scalar scalar string regexp pattern match C<match if $s =~ /$c/;> array scalar array entry existence C<match if 0E<lt>=$c && $cE<lt>@$s;> ref array entry definition C<match if defined $s-E<gt>[$c];> array entry truth C<match if $s-E<gt>[$c];> array array array intersection C<match if intersects(@$s, @$c);> ref ref (apply this table to all pairs of elements C<$s-E<gt>[$i]> and C<$c-E<gt>[$j]>) array regexp array grep C<match if grep /$c/, @$s;> ref hash scalar hash entry existence C<match if exists $s-E<gt>{$c};> ref hash entry definition C<match if defined $s-E<gt>{$c};> hash entry truth C<match if $s-E<gt>{$c};> hash regexp hash grep C<match if grep /$c/, keys %$s;> ref sub scalar return value definition C<match if defined $s-E<gt>($c);> ref return value truth C<match if $s-E<gt>($c);> sub array return value definition C<match if defined $s-E<gt>(@$c);> ref ref return value truth C<match if $s-E<gt>(@$c);>
In reality, Table 1 covers 31 alternatives, because only the equality and
intersection tests are commutative; in all other cases, the roles of
the $s
and $c
variables could be reversed to produce a
different test. For example, instead of testing a single hash for
the existence of a series of keys (match if exists $s->{$c}
),
one could test for the existence of a single key in a series of hashes
(match if exists $c->{$s}
).
As perltodo observes, a Perl case mechanism must support all these "ways to do it".
Adding a switch statement would require two new control structures:
switch
and case
. The switch
statement takes a single
parenthesized argument (the "switch value") of any scalar type,
including any reference type. A /.../
or m{...}
pattern may also
be specified and is automagically converted to a qr/.../
. A hash or array
may also be specified and is automatically enreferenced.
Regardless of the type of argument, its value is stored as the
current switch value in an inaccessible localized control variable.
The switch value is followed by a block, which may contain any statements,
including zero or more case
statements.
The case
statement takes a single scalar argument (which need be
parenthesized only if it's a variable), followed by a block. The case
statement selects the appropriate type of matching between that argument
and the current switch value, as determined by the respective types of
the switch value and the case
argument (see Table 1). If the match is
successful, the block associated with the case
statement is executed.
In most other respects, the case
statement is semantically identical
to an if
statement. For example, a case
statament can be followed
by an else
clause, and can be used as a postfix statement qualifier.
However, once the block of any case
statement finishes executing,
control is immediately transferred to the end of the enclosing switch
statement. The same effect may be obtained by executing a last
within
the block of the switch
. This means that, normally, there is no C-like
"fall-through" from a Perl case
statement. For example:
my $val = 7; switch ($val) { case [0..9] { print "single digit\n" } case [10..] { print "digits\n"; last } case 7 { print "lucky seven!\n" } }
would never print "lucky seven", since the first case
will always
succeed and transfer control out of the switch
.
However it is possible to cause a case
statement to "fall-through". If a
next
is executed within a case
block, control is immediately
transferred to the statement following the case
block.
For example:
switch ($count) { case 0 { print "nothing\n"; next } print "fell through!\n"; case /^\d$/ { print "single\n"; next } print "fell through again!\n"; case sub{$_[0]%2} { print "odd\n" } else { print "even\n" } }
%special = ( woohoo => 1, d'oh => 1 ); while (<>) { switch ($_) { case (%special) { print "homer\n"; last } # if $special{$_} case /a-z/i { print "alpha\n"; last } # if $_ =~ /a-z/i case [1..9] { print "small num\n"; last } # if $_ in [1..9] case sub { $_[0] >= 10 } { # if $_ >= 10 my $age = <>; switch (sub { $_[0] < $age } ) { case 20 { print "teens\n"; next } case 30 { print "twenties\n"; next } else { print "history\n"; next } } } } print "must be punctuation\n" case /\W/; # if $_ ~= /\W/ }
The use of intersection tests against an array reference is particularly useful for aggregating integral cases:
sub classify_digit { switch ($_[0]) { case 0 { return 'zero' } case [2,4,6,8] { return 'even' } case [1,3,4,7,9] { return 'odd' } case /[A-F]/i { return 'hex' } } }
switch
as an rvalueIt is also proposed that a switch
statement in a non-void context
might return the value of the last evaluated statement in the last
case
or else
it executes.
For example:
my $descr = switch ($num) { case 0 { 'zero' } case [2,4,6,8] { 'even' } case [1,3,4,7,9] { 'odd' } case /[A-F]/i { 'hex' } } my $index = switch ($index) { case ^_ < 0 { $length + $index } case [0..9] { $index } else { 9 } } my %args = switch (@_) { case 0 { %defaults } case ^_%2 { warn "Usage: foo(%args)"; (%args, undef) } else { %args } }
A sample (pure Perl) implementation is available on the CPAN as Switch.pm.
There is probably also good opportunity for compile-time optimization for constant (or inferrable) switch/case values.
RFC 23 : Higher order functions
RFC 24 : Semi-finite (lazy) lists
perltodo
perlsyn
perlfaq7
Christiansen, T., Message posted to comp.lang.perl.misc, September 1998, archived at language.perl.com
Torkington, N., Message posted to perl5-porters mailing list, February 1997, archived at www.xray.mpe.mpg.de
Barr, G., Message posted to perl5-porters mailing list, October 1997, archived at www.xray.mpe.mpg.de