Day 16: We call it ‘the old switcheroo’

by

Another glorious day in Advent; another gift awaits us. It’s switch statements!

Well, the term for them is still “switch statement” in Perl 6, but the keyword has changed for linguistic reasons. It’s now given, as in “given today’s weather”:

given $weather {
  when 'sunny'  { say 'Aah! ☀'                    }
  when 'cloudy' { say 'Meh. ☁'                    }
  when 'rainy'  { say 'Where is my umbrella? ☂'   }
  when 'snowy'  { say 'Yippie! ☃'                 }
  default       { say 'Looks like any other day.' }
}

Here’s a minimal explanation of the semantics, just to get us started: in the above example, the contents of the variable $weather is tested against the strings 'sunny', 'cloudy', 'rainy', and 'snowy', one after the other. If either of them matches, the corresponding block runs. If none matches, the default block triggers instead.

Not so different from switch statements in other languages, in other words. (But wait!) We’ll note in passing that the when blocks don’t automatically fall through, so if you have several conditions which would match, only the first one will run:

given $probability {
  when     1.00 { say 'A certainty'   }
  when * > 0.75 { say 'Quite likely'  }
  when * > 0.50 { say 'Likely'        }
  when * > 0.25 { say 'Unlikely'      }
  when * > 0.00 { say 'Very unlikely' }
  when     0.00 { say 'Fat chance'  }
}

So if you have a $probability of 0.80, the above code will print Quite likely, but not Likely, Unlikely etc. (In the cases when you want to “fall through” from a when block, you can end it with the keyword continue.) (Update: after spec discussions that originated in the comments of this post, break/continue were renamed to succeed/proceed.)

Note that in the above code, strings and decimal numbers and comparisons are used as the when expression. How does Perl 6 know how to match the given value against the when value, when both can be of wildly varying types?

The answer is that the two values enter a negotiation process called smartmatching, mentioned briefly in Day 13. To summarize, smartmatching (written as $a ~~ $b) is a kind of “regex matching on steroids”, where the $b doesn’t have to be a regex, but can be of any type. For ranges, the smartmatch will check if the value we want to match is within the range. If $b is a class or a role or a subtype, the smartmatch will perform a type check. And so on. For values like Num and Str which represent themselves, some appropriate equivalence check is made.

The “whatever star” (*) has the peculiar property that it smartmatches on anything. Oh, and default is just sugar for when *.

To summarize the summary, smartmatching is DWIM in operator form. And the given/when construct runs on top of it.

Now for something slightly head-spinning: the given and when features are actually independant! While you complete the syllable “WHAT?”, let me explain how.

Given is actually a sort of once-only for loop.

given $punch-card {
  .bend;
  .fold;
  .mutilate;
}

See what happened there? All given does is set the topic, also known to Perl people as $_. The cute .method is actually short for $_.method.

Now it’s easier to see how when can be used without a given, too. when can be used inside any block which sets $_, implicitly or explicitly:

my $scanning;
for $*IN.lines {
  when /start/ { $scanning = True }
  when /stop/  { $scanning = False }

  if $scanning {
    # Do something which only happens between the
    # lines containing 'start' and 'stop'
  }
}

Note that those when blocks exhibit the same behaviour as the in a given block: they skip the rest of the surrounding block after executing, which in the above code means they go directly to the next line in the input.

Here’s another example, this time with $_ explicitly set:

sub fib(Int $_) {
  when * < 2 { 1 }
  default { fib($_ - 1) + fib($_ - 2) }
}

(This independence between given and when plays out in other ways too. For example, the way to handle exceptions is with a CATCH block, a variant of given which topicalizes on $!, the variable holding the most recent exception.)

To top it all off, both given and when come in statement-ending varieties, just as for, if and the others:

  say .[0] + .[1] + .[2] given @list;
  say 'My God, it's full of vowels!' when /^ <[aeiou]>+ $/;

You can even nest a when inside a given:

  say 'Boo!' when /phantom/ given $castle;

As given and when represent another striking blow against the Perl obfuscation track record, I hereby present you with the parting gift of an obfu DNA helix, knowing full well that it doesn’t quite make up for the damage caused. :)

$ perl6 -e 'for ^20 {my ($a,$b)=<AT CG>.pick.comb.pick(*);\
  my ($c,$d)=sort map {6+4*sin($_/2)},$_,$_+4;\
  printf "%{$c}s%{$d-$c}s\n",$a,$b}'
     G  C
      TA
     C G
   G    C
 C     G
 G     C
 T   A
  CG
 CG
 C   G
 T     A
  T     A
   T    A
     C G
      TA
    T   A
  T     A
 A     T
 C    G
 G  C
About these ads

24 Responses to “Day 16: We call it ‘the old switcheroo’”

  1. VZ Says:

    Thanks for the nice article with the examples! I can’t help wondering about something though: if “continue” inside “when” makes it fall through to the next when clause but “when” inside a for loop already implicitly continues (because it skips the rest of the block by default), doesn’t it mean that using “continue” inside “when” inside “for” has the effect exactly opposite to that of using “continue” inside “for” itself directly? If so, this would be extremely confusing IMHO so I really hope I’m missing something here…

    • carl Says:

      Let me guess; you’re a Java programmer? :)

      What you’re used to as ‘continue’ (with the semantics ‘go to next iteration in loop’) is called ‘next’ in Perl. In fact, all three keywords which control the flow in loops are “four-letter words” (next, last, redo), and line up nicely if you write them in a bunch at the start of the loop block.

      The function of ‘continue’ in Perl 6 is only to counteract the default behaviour of ‘when’ blocks, so they fall through instead of terminating the innermost surrounding topicalizer ($_-setting) block.

      • VZ Says:

        > Let me guess; you’re a Java programmer?

        No, a C/C++ one (is this less or more embarrassing?). But normally I still know better than to use “continue” in my Perl code :-/ Sorry, next time I’ll avoid reading Perl6 blogs so early in the morning.

        But even though it might look like I’m clutching at straws, I still find “continue” in this context somewhat counter-intuitive. Probably it’s just my C-ish DNA which colours my perception of it…

      • carl Says:

        I wasn’t aiming to embarrass either way. ;) Just useful to know someone’s background when explaining semantics.

        I’m sure if you have a better suggestion than ‘continue’, people with commit bits to the spec will listen attentively. I’ve also looked at the term occasionally and found it unfortunate that it collides with its C-family homonym.

      • VZ Says:

        I don’t know if this is by some chance related to this discussion but according to http://lith-ology.blogspot.com/2009/12/seven-days-between-parrot-and-camel_22.html “proceed” is the new keyword replacing “continue” inside a when clause. This seems to be much nicer to me, thanks to whoever came up with it!

      • carl Says:

        VZ, I don’t have the whole story of the change, but it was definitely triggered by this discussion.

        Consider yourself having had a direct influence on a Perl 6 keyword!

  2. anders! Says:

    For overloading smart matching, am I right in assuming is just a plain ol’ `multi sub infix:<~~>`?

    • carl Says:

      That might work in a limited number of cases, but you’re much better off providing your class (or subtype, or whatever) with an .ACCEPTS method. That’s how smartmatching actually works under the hood.

      Here’s an example, to get you started:

      $ perl6 -e 'role DivisibleBy[Int $n] { method ACCEPTS($obj) { $obj !% $n } };\
        class DivisibleByThree does DivisibleBy[3] {}; \
        say $_ ~~ DivisibleByThree.new for 15, 20'
      1
      0
      
  3. Doug Says:

    Wait … so does this mean that ‘when’ is mostly just syntactic sugar for ‘if … elseif … else’ + ‘last’ and smart matching? Doesn’t seem to add much in terms of new features, but there’s something to be said for code cleanliness.

    • colomon Says:

      Yes, mostly. The syntax for the conditional part is a touch different between if and when, but “mostly” just syntactic sugar is correct.

      But it is “just right” syntactic sugar, IMO. Much like say (which is 99% just print with "\n" tacked on), once you’ve incorporated it into your programming, it feels absolutely essential.

    • Joe Z Says:

      *chuckle*

      We posted the same question in parallel. I guess an answer to any(Doug, Joe Z) would work. ;-)

  4. Joe Z Says:

    First, I’d like to say “HURRAY!” for this use of “given”:

    given $punch-card {
      .bend;
      .fold;
      .mutilate;
    }
    

    I was just telling someone the other day that the only feature that I really miss from my Pascal days almost two decades ago was its “with record do” construct, and that’s almost exactly what “given” accomplishes in that example.

    Second, I have a question: If I understood correctly, the “when” clause implicitly terminates the block it’s in whenever it fires. Am I correct in thinking that a series of “when” clauses look more like a bunch of nested “if-else” blocks? ie:

    {
      when /start/ { $scanning = True }
      when /stop/  { $scanning = False }
    
      if $scanning {
        # Do something which only happens between the
        # lines containing 'start' and 'stop'
      }
    }
    

    Is that then equivalent to:

    {
      if /start/ { $scanning = True }
      else {
        if /stop/  { $scanning = False }
        else {
          if $scanning {
            # Do something which only happens between the
            # lines containing 'start' and 'stop'
          }
        }
      }
    }
    

    (I’ve purposefully ignored “continue” for the moment.)

    Does that mean a stray “when” could accidentally short circuit a ton of program code? For example, if I put a “when” at the top level of my program and it fires, would it effectively terminate my program at that point?

    • carl Says:

      As for the last question: no. The Perl 6 specification states that a when block not found within a topicalizer block (one which sets $_) constitutes a compile-time error.

    • patch Says:

      As carl mentioned, this can also be accomplished by passing a scalar (really a single element list) to a for loop. Here’s an example in Perl 5.

      for ($punch_card) {
        $_->bend;
        $_->fold;
        $_->mutilate;
      }
      

      I must admit that given is much clearer though, especially for those who don’t know the once-only loop idiom. I commonly use it when I need to perform several substitutions or transliterations on one string.

      for ($phone) {
        tr{ABC} {2};
        tr{DEF} {3};
        tr{GHI} {4};
        tr{JKL} {5};
        tr{MNO} {6};
        tr{PQRS}{7};
        tr{TUV} {8};
        tr{WXYZ}{9};
      }
      

      However, for this example we won’t even need given in Perl 6.

      $phone.=trans(
        ABC  => 2,
        DEF  => 3,
        GHI  => 4,
        JKL  => 5,
        MNO  => 6,
        PQRS => 7,
        TUV  => 8,
        WXYZ => 9,
      );
      
  5. Roie Says:

    Am I correct in understanding that the word “default” is for all intents and purposes optional? If all the “when”s fail then the code underneath will be executed whether or not it is in a “default” block. (Also, any code after the “default” block is dead and will never be executed, right?)

  6. sam Says:

    why isn’t it ‘as’ instead of ‘when’? that’d save you from typing 2 extra characters each time.

    • Moritz Says:

      I think as would be pretty ambiguous, whereas when clearly marks branches.

      I know that when is four characters, but it’s very easy to type on most keyboards because first and third characters are typed with the left hand, and second and fourth with the right hand :-)

  7. Paul Tötterman Says:

    I think there’s a typo in the fib-function. If fib(0) and fib(1) return 0, the series will never grow. They should return 1.

  8. Sitaram Says:

    Could you elaborate on the “*”. For example, I notice that the fib() example works even if I replace the lone “*” with “$_”.

    Is “*” just a 50% shorter version of $_ under special cases? I can’t believe that’s all it is :)

    • carl Says:

      You’re right, that isn’t all it is. But the fib() example is not optimal to show the difference, so allow me to choose two other, clearer ones.

      You want to add forty-two to every number in @numbers. So you use a map, naturally. With $_, it looks like this: map { $_ + 42 }, @numbers; with *, it looks like this: map * + 42, @numbers.

      From the first example, we see that * is short not only for $_, but also for the surrounding {} block.

      Now, think about what @numbers[$_ - 5] means. It means “the element in @numbers five places before what’s in $_“. (For the example, it doesn’t much matter what $_ itself contains.)

      Conversely, what does @numbers[* - 5] mean? It means “the elemens in @numbers five places before *mumble-mumble*”. Or, in code, @numbers[ { $_ - 5 } ]. The mumbling comes from the fact that the $_ won’t be evaluated until the block is run, and a $_ value is sent in as a parameter. Now, it so happens that array indexings evaluate blocks by sending in their number of elements (their “length”) as a parameter to the block, so the code actually comes to mean @numbers[ @numbers - 5 ], or “the fifth last element in @numbers“.

      Summing up, * means not just $_, but $_ in a code block which is possibly run at a later time, when it’s more convenient, with a value of $_ very much decided by the surrounding context.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

Join 37 other followers

%d bloggers like this: