Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

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.

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.

    CountZero

    "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?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://441778]
Approved by Corion
Front-paged by ww
help
Chatterbox?
[ambrus]: Ok, the docs is somewhat unclear. It does say that when an object is garbage collected, it will get cleaned up, and eventually can no longer get messages. It's not clear how long this takes, eg. I think it's kept alive until its queued events are handled
[ambrus]: in the loop, and I'm not sure if that's ok for AnyEvent. Also, it's not clear if a Timer or File object you free really is garbage collected, i.e. that Prima doesn't keep some references to it, but I hope so.
[Corion]: choroba: No, I don't remember that story, but yes, it matches my experience ;))
[ambrus]: Hopefull the object isn't kept alive, the events are processed immediately, but you'd have to read a lot of source code to be sure about that.
[Corion]: ambrus: I think both of AnyEvent and Prima are pretty tight in their memory management because they both are cooperative multitasking and (I think) both use the Perl memory management for managing things
[Corion]: ambrus: And for Windows, I don't think that Prima knows if there still are messages queued for an object (in the Windows message loop). Finding that out would take lots of effort for little gain
[ambrus]: And even if this works, I'm still not sure you can't get double timeouts from a Timer.
[ambrus]: Corion: well Prima::Object says something like that the cleanup method will send an onDestory message and that you can't get more messages after cleanup, or something.
[Corion]: ambrus: Yeah - I don't think the deep source dive will be necessary if things are implemented as simple as they could be :)) And hopefully I won't need (more) timely object destruction. I can update the screen at 60Hz and hopefully even do HTTP ...
[Corion]: ... transfers in the background. Now that I think about it, this maybe even means that I can run the OpenGL filters on Youtube input :)

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (8)
As of 2016-12-09 10:25 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    On a regular basis, I'm most likely to spy upon:













    Results (150 votes). Check out past polls.