Perl 5.10 introduced the given-when
statement, and Perl 5.12 refines it slightly by letting you use the when
as a statement modifier. A statement modifier puts the conditional expression at the end of the statement (see perlsyn). You’ve probably already used many of these:
print "It's too darned hot!\n" if $city eq 'Baltimore'; print "I'm this far!\n" unless $Quiet; print "An element is $_.\n" foreach @array; print "Found cat at @{[pos]}\n" while /cat/g; do { $count++; $i_am_bored = int rand 2 } until $i_am_bored;
In Perl 5.10, you have to put the when
first, put parentheses around the conditional expression, and follow it with a block:
use 5.012; my %microchips = ( 'Mimi' => 123, 'Buster' => undef, 'Roscoe' => 345, ); my @array = ( 5 .. 7 ); foreach ( 1 .. 10, qw(Mimi Buster) ) { when( @array ) { say "$_: in array" } when( %microchips ) { say "$_: in hash" } }
Perl 5.12 allows you to use the when
as a statement modifier instead, which means that you can put the when
after the expression that you want to run:
use 5.012; ...; foreach ( 1 .. 10, qw(Mimi Buster) ) { say "$_: in array" when( @array ); say "$_: in hash" when( %microchips ); }
Just like you can with the other statement modifiers, you can omit the parentheses around the condition:
use 5.012; ...; foreach ( 1 .. 10, qw(Mimi Buster) ) { say "$_: in array" when @array; say "$_: in hash" when %microchips; }
As a statement modifier, the expression before the when
has an implicit break
when you use it with given
or an implicit next
when you use it with foreach
. That is, once a when
condition evaluates to true, Perl doesn’t continue with the rest of the block.
The foreach
case actually looks like this:
use 5.012; ...; foreach ( 1 .. 10, qw(Mimi Buster) ) { do { say "$_: in array"; next } when @array; do { say "$_: in hash"; next } when %microchips; }
And the given
case actually looks like:
use 5.012; ...; given ( $cat ) { do { say "$_: in array"; break } when @array; do { say "$_: in hash"; break } when %microchips; }
You’re still supposed to use when
inside a topicalizer (e.g. foreach
or given
). You might be tempted to use it more liberally so you can take advantage of its implicit smart matching anywhere that you like. A curious and perhaps unintended parsing makes it a runtime error instead of a compilation error:
use 5.012; my %microchips = ( 'Mimi' => 123, 'Buster' => undef, 'Roscoe' => 345, ); while( <STDIN> ) { chomp; # this works (without a warning) as long as the value in $_ # is not a hash key say "Found cat with id [$microchips{$_}]" when %microchips; }
The while
isn’t a topicalizer, even though in this particular idiom it sets $_
for you. You get the output along with a warning:
$ perl5.12.1 no-topicalizer.pl Buster Found cat with id [] Can't use when() outside a topicalizer at test line 11, <STDIN> line 1.
However, you don’t get a fatal error when the condition is false, which is probably another bug (filed as RT #77510). There’s no fatal error, no warning message, and the output shows that Perl kept going:
Ella Mimi Found cat with id 123 Can't use when() outside a topicalizer at test line 11, <STDIN> line 2.
Just because it it does just what you think it should do then dies telling you it doesn’t do that, don’t think you should do it. You could use eval
to catch the fatal error, but that’s a kludge that will only work until the Perl developers fix the problem. Besides, if you are going to go that far, it’s just as easy to make the smart match explicit with an if
statement modifier (and that is even backward compatible with 5.10):
use 5.010; my %microchips = ( 'Mimi' => 123, 'Buster' => undef, 'Roscoe' => 345, ); while( <STDIN> ) { chomp; say "Found cat with id $microchips{$_}" if $_ ~~ %microchips; }
The only question now is how long you will be able to use this before your Perl::Critic policies update to forbid it, whether inside or outside of a topicalizer. Use it while you can: time is running out.
This was really nice. I did not know you could use when() as a statement modifier. One minor thing that was confusing to me was where you wrote “As a statement modifier, the expression before the when has an implicit break …”. From my first reading of that, I thought the implicit break happened only when you use when() as a statement modifier, as opposed to the traditional way. It might be better to write that as, “The when expression has an implicit break …”
This reply is a bit late, but I wonder why while(){} isn’t a topicalizer… I’m not seeing the trouble that using when inside a while loop would cause.
Good article BTW.
Asking why
while
isn’t a topicalizer the same answer as asking whyif
isn’t one. It’s not what they do. Thewhile( <FH>)
idiom is just a special case that happens to set$_
for you, but only in that one case. Most of the possible uses ofwhile
don’t set$_
or a named control variable, while all of the uses of the topicalizers do.