[% setvar title Control flow: Builtin switch statement %]

This file is part of the Perl 6 Archive

Note: these documents may be out of date. Do not use as reference!

To see what is currently happening visit http://www.perl6.org/

TITLE

Control flow: Builtin switch statement

VERSION

  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

ABSTRACT

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.

BACKGROUND

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.

Table 1: Matching a switch value ($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".

DESCRIPTION

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

Larger examples

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

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

IMPLEMENTATION

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.

REFERENCES

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