Effective Perl Programming – write better, more idiomatic Perl

Use a smart match to match several patterns at once

The smart match operator (Item 23. Make work easier with smart matching) reduces many common comparisons to a few keystrokes, keeping with Perl’s goal of making the common things easy. You can use the smart match operator to make even less common tasks, such as matching many regular expressions at the same time, just as easy. This Item shows you how to use the smart match to see if at least one of a series of regexes matches a string.

You use next to skip the rest of the patterns once you find a matching one. The task is slightly different if you want to find which patterns matched. In that case you have to always go through all of them.

You can improve on this code slightly by making regular expression objects (Item 40. Pre-compile regular expressions):

You can even do it without the map if you like so you have more control over each pattern:

my @patterns = (qr/foo/, qr/ba(r|z)/, qr/quux/);

You’ll come back to this in a moment.

A note on short circuiting

If you need to try all of the patterns and check side effects at each step, you don’t want to use the smart match code in this Item.

Short-circuiting has consequences. The common case is the values of capture buffers. If more than one pattern can match, only the first one will set capture buffers. If you don’t know what which pattern matched you might have a tough time figuring out where the values in the capture buffers came from.

There is another case, which you may hope is rare (but even the rare, multi-million dollar mistake is still a multi-million dollar mistake). If the patterns have side effects that you care about and not all of the patterns run, some side effects won’t affect sidely.

This series of matches has two patterns that will match foo. The map inserts a (?{...}), the regular expression sequence the evaluates some Perl code during a match:

That should be a rare case, but that doesn’t make it any less fragile. Again, if you need all the patterns to at least try to match, you don’t want to use the code in this Item.

Double smart matching

The smart match operator knows how to handle regular expressions and it already knows how to match a scalar against an array. You probably already know that you can see if a scalar is an element of an array using the smart match:

my @strings = qw( Buster Mimi Ginger Ella );
$_ ~~ @strings;

The smart match knows what to do based its arguments. If there’s a simple scalar on each side, it does either a string or number equality comparison. That’s as far as many people think about it though. When the smart match looks at the elements in the array, it does a smart match with the scalar and each element in the array. That is, the comparison operator for any element can be different from any other.

Here’s a smart match against an array, @tests, that has several different types of elements, including a string, a regex object, a subroutine reference, and an anonymous array:

The smart match looks at the first element in @tests, sees that it’s a regex object and tries a pattern match. If that fails, it looks at the second element, which is a subroutine reference. It passes $_ as the argument to the subroutine, which can then do anything it likes. If the subroutine reference returns true, the smart match succeeds. You can see the non-Buster values go through the subroutine on the way to subsequent checks:

Notice that Ella matched. The final element in @tests is another array reference, so the smart match goes through the elements of that second-level array testing each of those items with another series of smart matches. Neat, huh?

Yes, smart matching on Perl 5.10.0 is broken. You shouldn’t use smart matching with that version. However, there’s also a wider point for the effective Perler here. Don’t use .0 point releases. It’s not until real people start using these releases that the developer (perl or anything else) see the errors of their ways and fix them with a .1 release.

I’ve updated the use 5.010001 statements to reflect that, which I should have done previously.

I wish the smart operator would test if a key exists before testing equality when given something like $hash{$key} on one side. I still use ‘exists $hash{$key} and $val ~~ $hash{$key}’ instead of just $val ~~ $hash{$key} because it speeds up the case where $hash{$key} doesn’t exist by a significant margin.