Use when() as a statement modifier

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.

Leave a comment

3 Comments.

  1. 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 …”

  2. 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 why if isn’t one. It’s not what they do. The while( <FH>) idiom is just a special case that happens to set $_ for you, but only in that one case. Most of the possible uses of while don’t set $_ or a named control variable, while all of the uses of the topicalizers do.

Leave a Reply


[ Ctrl + Enter ]

7ads6x98y