Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic

Switch/case (given/when) in Perl5

by Roy Johnson (Monsignor)
on Mar 23, 2005 at 14:30 UTC ( #441778=perlmeditation: print w/ replies, xml ) Need Help??

Occasionally, people lament that Perl has no switch/case statement like C. Perl6 has given/when, which goes several steps beyond the simple switch by handling arbitrary conditions, rather than simple equivalence.

So what does it buy you over if-else? Not a lot, in my experience. You can save some keystrokes by not retyping what you're matching against, and sometimes the ability to control whether things "fall through" -- matching multiple cases -- can come in handy.

Of course, we are not entirely without alternatives in Perl5. For simple multi-candidate equality testing without defaulting or fall-through, you can use a hash:

my $consider = 'foo'; ${{ bar => sub { print "Barred!\n" }, foo => sub { print "Fooed for thought...\n" } }}{$consider}();
Grep is a natural tool for multi-way matching, and you can set it up to do fall-through and defaulting, though you do have to work at it:
{ my $continue = 1; $_->[1]() for grep { my ($cond, $then, $break)=@$_; local $_='boz'; # This is your given $continue and $cond->() and do { $continue = !defined $break; 1} } [sub {/z/}, sub { print "Hi!\n" }], [sub {/o/}, sub { print "Oh!\n" }, 'break'], [sub {1}, sub { print "This is the default\n" }] ; }
Clearly you could encapsulate this in a function that takes your given and the list of condition-then(-break) sub pairs/triplets.

Still not pretty enough? Then maybe I can interest you in something that looks a lot like Perl6's given (even though I think it should have been called "consider" for better reading):

{ my $continue = 1; sub given ($$) { local $_ = shift; my $next = shift; my ($cond, $then); while ($next) { ($cond, $then, $next) = @$next; $then->() if $cond->() and $then; $continue or last; } } sub when (&$) { [$_[0], @{$_[1]}] } sub then (&;$) { [@_] } sub default(&) { [@_] } sub done() { $continue = 0 } sub isit ($$) { my @args=@_; [sub { $_ eq $args[0] }, @{$args[1]}] +} } given 'baz', isit 'baz', then { print "String match!\n" } when {/z/} then { print "Hi!\n" } when {/o/} then { print "Oh!\n"; done } default { print "This is the default\n" } ;
Given can only consider a scalar, but otherwise it's pretty comparable to the Perl6 critter, eh?

Update: added isit sub, to provide implicit eq check against $_. Writing when {$_ eq 'something'} for a lot of cases would get pretty old.

Caution: Contents may have been coded under pressure.

Comment on Switch/case (given/when) in Perl5
Select or Download Code
Replies are listed 'Best First'.
Re: Switch/case (given/when) in Perl5
by Roy Johnson (Monsignor) on Mar 23, 2005 at 15:21 UTC
    Developing this, I originally wanted to have the simpler syntax of when {cond-block} {then-block} but prototypes won't let me do that. You can only get one block interpreted as an anonymous sub without specifying sub, so i had to introduce the then pseudokeyword.

    The same thing is what makes the grep solution so clunky. So meditation #2: we need a qsub operator. You put blocks of code after it, and it gives you a list of coderefs back. Then the arguments to grep look like:

    [qsub {/z/} { print "Hi!\n" }], [qsub {/o/} { print "Oh!\n" } 'break'], [qsub {1} { print "This is the default\n" }]
    Ah, the 'break'. Well, its value doesn't matter, so I could just stick {} in there instead. Or parenthesize qsub's list and stick a comma before 'break'...or qsub could leave things that aren't in braces alone. Smart qsub. Want to include a hashref in your list? Stick a + on the front, or put it in parentheses.

    Update: Hold the phone! You don't need a new operator! You just need new semantics: if a block appears before or after any other object in list context, and they're not separated by a comma, the block is a coderef. Normal executable blocks are in void context; hashrefs will either appear separated by commas or alone. Nothing breaks. Right? [Wrong: indexing of hashes breaks (or at least becomes ambiguous): (1, $hash {'index'}) — dang.] Hashrefs adjacent to coderefs can be disambiguated with the old + trick.

    Caution: Contents may have been coded under pressure.
Re: Switch/case (given/when) in Perl5
by Anonymous Monk on Mar 23, 2005 at 17:16 UTC
    Occasionally, people lament that Perl has no switch/case statement like C. Perl6 has given/when, which goes several steps beyond the simple switch by handling arbitrary conditions, rather than simple equivalence.
    One drawback of Perl6's given/when over a "simple" switch is that given/when is just a glorified if/else chain. Perl will have to check each clause in order to find a matching one. Years ago, I programmed in a language (some kind of Pascal/C bastard) where constructs like:
    switch (... EXPRESSION ...) { case CONST1: ....; case CONST2, CONST3: ....; case CONST4 .. CONST5: ....; /* Range! */ case CONST6: ....; default; }
    were optimized to do a binary search instead of a linear search. Of course, you can only do that if you only have constants in your cases, but I found it pretty neat.
      There's no reason an if/else chain can't be optimized to a jump table where appropriate. In fact, Perl 4 did that optimization, though we never got around to putting it into Perl 5. Anyway, if you think Perl 6's switch structure precludes such an optimization, you are simply wrong. There's no reason to test any expression that you already know the results of, so such a Perl 6 switch statement can be made just as fast as a C-style switch statement if you limit the cases to constants. But Perl can extend the optimization to the case of strings with unique prefixes, and to ordinary if/else cascades as well, while retaining all the benefits of cascading logic in the more dynamic cases.

        Do you think someone will get around to adding the optimisation this time?

        Or will 6.8.x come around and still there will be no optimisations that require looking beyond the current opcode?

Re: Switch/case (given/when) in Perl5
by CountZero (Bishop) on Mar 23, 2005 at 21:54 UTC
    There is of course the Switch-module.


    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

      There is of course the Switch-module.
      Which I used happily, until one day I needed to run some "use Switch" code through the perl debugger, and learned that the convienience was not worth it.

      Since then, I have of course heard the rap about how source filters are dangerous and wasteful, but "impossible to debug" is good enough (I mean, bad enough) for me.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://441778]
Approved by Corion
Front-paged by ww
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others imbibing at the Monastery: (9)
As of 2015-11-30 08:56 GMT
Find Nodes?
    Voting Booth?

    What would be the most significant thing to happen if a rope (or wire) tied the Earth and the Moon together?

    Results (766 votes), past polls