The Smart Match Operator
The smart match operator, ~~
, looks at both of its operands and decides on its own how it should compare them. If the operands look like numbers, it does a numeric comparison. If they look like strings, it does a string comparison. If one of the operands is a regular expression, it does a pattern match. It can also do some complex tasks that would otherwise take a lot of code, so it keeps you from doing too much typing.
examples:
use 5.010;
# these 2 are the same
print "I found Fred in the name!\n" if $name =~ /Fred/;
say "I found Fred in the name!" if $name ~~ /Fred/;
The smart match operator starts to show its power with more complex operations. Suppose you wanted to print a message if one of the keys in the hash %names
matches Fred. You can’t use exists
because it only checks for the exact key. You could do it with a foreach
that tests each key with the regular expression operator, skipping those that don’t match. When you find one that does match, you can change the value of $flag
and skip the rest of the iterations with last
:
my $flag = 0;
foreach my $key ( keys %names ) {
next unless $key =~ /Fred/;
$flag = $key;
last;
}
print "I found a key matching 'Fred'. It was $flag\n" if $flag;
Whew! That was a lot of work just to explain it, but it works in any version of Perl 5. With the smart match operator, you just need the hash on the lefthand side and the regular expression operator on the righthand side:
use 5.010;
say "I found a key matching 'Fred'" if %names ~~ /Fred/;
The smart match operator knows what to do because it sees a hash and a regular expression. With those two operands, the smart match operator knows to look through the keys in %names and apply the regular expression to each one. If it finds one that matches, it already knows to stop and return true. It’s not the same sort of match as the scalar and regular expression. It’s smart; it does what’s right for the situation. It’s just that the operator is the same, even though the operation isn’t.
If you want to compare two arrays (limiting them to the same size just to make things simpler), you could go through the indices of one of the arrays and compare the corresponding elements in each of the arrays. Each time the corresponding elements are the same, you increment the $equal
counter. After the loop, if $equal
is the same as the number of elements in @names1
, then the arrays must be the same:
my $equal = 0;
foreach my $index ( 0 .. $#names1 ) {
last unless $names1[$index] eq $names2[$index];
$equal++;
}
print "The arrays have the same elements!\n" if $equal == @names1;
With smart match operator, the code can be quite simple:
use 5.010;
say "The arrays have the same elements!" if @names1 ~~ @names2;
Okay, one more example. Suppose you call a function and want to check that its return value is one of a set of possible or expected values. Going back to the max()
subroutine, you know that max()
should return one of the values you passed it. You could check that by comparing the return value of max to its argument list using the same techniques as the previous hard ways:
use List::Util qw[max min];
my @nums = qw( 1 2 3 27 42);
my $result = max(@nums);
my $flag = 0;
foreach my $num ( @nums ) {
next unless $result == $num;
$flag = 1;
last;
}
print "The result is one of the input values\n" if $flag;
With smart match operator, the code would be:
use 5.010;
my @nums = qw( 1 2 3 27 42 );
my $result = max( @nums );
say "The result [$result] is one of the input values (@nums)" if @nums ~~ $result;
You can also write that smart match with the operands in the other order and get the same answer. The smart match operator doesn’t care which operands are on which side. The smart match operator ~~
is no longer commutative. See https://metacpan.org/pod/release/JESSE/perl-5.12.2/pod/perl5120delta.pod#Smart-match-changes for more details.
use 5.010;
my @nums = qw( 1 2 3 27 42 );
my $result = max( @nums );
say "The result [$result] is one of the input values (@nums)" if $result ~~ @nums;
The smart match operator is commutative, which you may remember from high school algebra as the fancy way to say that the order of the operands doesn’t matter.
Smart Match Precedence
Below is a table showing what smart match operator can do and their precedence:
Smart match operations for pairs of operands
Example | Type of match |
---|---|
%a ~~ %b | Hash keys identical |
%a ~~ @b | At least one key in %a is in @b |
%a ~~ /Fred/ | At least one key matches pattern |
%a ~~ 'Fred' | Hash key exists $a{Fred} |
@a ~~ @b | Arrays are the same |
@a ~~ /Fred/ | At least one element matches pattern |
@a ~~ 123 | At least one element is 123, numerically |
@a ~~ 'Fred' | At least one element is 'Fred', stringwise |
$name ~~ undef |
$name is not defined |
$name ~~ /Fred/ |
Pattern match |
123 ~~ '123.0' | Numeric equality with “numish” string |
'Fred' ~~ 'Fred' | String equality |
123 ~~ 456 | Numeric equality |
When you use the smart match operator, Perl goes to the top of the chart and starts looking for a type of match that corresponds to its two operands. It then does the first type of match it finds. The order of the operands doesn’t matter.
The given Statement
The given-when
control structure allows you to run a block of code when the argument to given
satisfies a condition. It’s Perl’s equivalent to C’s switch
statement, but as with most things Perly, it’s a bit more fancy, so it gets a fancier name.
Here’s a bit of code that takes the first argument from the command line, $ARGV[0]
, and goes through the when conditions to see if it can find Fred. Each when
block reports a different way that it found Fred, starting with the least restrictive to the most:
use 5.010;
given( $ARGV[0] ) {
when( /fred/i ) { say 'Name has fred in it' }
when( /^Fred/ ) { say 'Name starts with Fred' }
when( 'Fred' ) { say 'Name is Fred' }
default { say "I don't see a Fred" }
}
The given
aliases its argument to $_
,* and each of the when
conditions tries an implicit smart match against $_
. You could rewrite the previous example with explicit smart matching to see exactly what’s happening:
use 5.010;
given( $ARGV[0] ) {
when( $_ ~~ /fred/i ) { say 'Name has fred in it' }
when( $_ ~~ /^Fred/ ) { say 'Name starts with Fred' }
when( $_ ~~ 'Fred' ) { say 'Name is Fred' }
default { say "I don't see a Fred" }
}
Note*: In Perl parlance, given
is a topicalizer because it makes its argument the topic, the fancy new name for $_
in Perl 6.
Unless you say otherwise, there is an implicit break at the end of each when block, and that tells Perl to stop the given-when
construct and move on with the rest of the program. The previous example really has breaks in it, although you don’t have to type them yourself:
use 5.010;
given( $ARGV[0] ) {
when( $_ ~~ /fred/i ) { say 'Name has fred in it'; break; }
when( $_ ~~ /^Fred/ ) { say 'Name starts with Fred'; break; }
when( $_ ~~ 'Fred' ) { say 'Name is Fred'; break; }
default { say "I don't see a Fred"; break; }
}
If you use continue
at the end of a when
instead, Perl tries the succeeding when statements too, repeating the process it started before. That’s something that if-elsif-else
can’t do. When another when
satisfies its condition, Perl executes its block (again, implicitly breaking at the end of the block unless you say otherwise). Putting a continue
at the end of each when
block means Perl will try every condition:
given( $ARGV[0] ) {
when( $_ ~~ /fred/i ) { say 'Name has fred in it'; continue; }
when( $_ ~~ /^Fred/ ) { say 'Name starts with Fred'; continue; }
when( $_ ~~ 'Fred' ) { say 'Name is Fred'; }
default { say "I don't see a Fred"; }
}
!!!Note!!!: when testing with "Fredx", you will get the first 2 when
and the default
executed. See this post for details: http://grokbase.com/t/perl/beginners/10bthx24xk/given-when-problem
Dumb Matching
Although the given-when
can use smart matching, you can use the “dumb” comparisons that you already know. It’s not really dumb, it’s just the regular matching that you already know. When Perl sees an explicit comparison operator (of any type) or the binding operator, it does only what those operators do:
given( $ARGV[0] ) {
when( $_ =~ /fred/i ) { say 'Name has fred in it'; continue; }
when( $_ =~ /^Fred/ ) { say 'Name starts with Fred'; continue; }
when( $_ =~ 'Fred' ) { say 'Name is Fred'; }
default { say "I don't see a Fred"; }
}
You can even mix and match dumb and smart matching; the individual when
expressions figure out their comparisons on their own:
use 5.010;
given( $ARGV[0] ) {
when( /fred/i ) { #smart
say 'Name has fred in it'; continue }
when( $_ =~ /^Fred/ ) { #dumb
say 'Name starts with Fred'; continue }
when( 'Fred' ) { #smart
say 'Name is Fred' }
default { say "I don't see a Fred" }
}
Note that the dumb and smart match for a pattern match are indistinguishable since the regular expression operator already binds to $_
by default.
There are certain situations in which Perl will automatically use dumb matching. You can use the result of a subroutine inside the when, in which case, Perl uses the truth
or falseness
of the return value:
use 5.010;
given( $ARGV[0] ) {
when( name_has_fred( $_ ) ) { #dumb
say 'Name has fred in it';
continue
}
}
The subroutine call rule also applies to the Perl built-ins defined
, exists
, and eof
too, since those are designed to return true or false. Negated expressions, including negated regular expressions, don’t use a smart match either. These cases are just like the control structure conditions you saw in previous chapters.
when with Many Items
Sometimes you’ll want to go through many items, but given
takes only one thing at a time. In this case, you could wrap given
in a foreach
loop. If you wanted to go through @names
, you could assign the current element to $name
, then use that for given
:
use 5.010;
foreach my $name ( @names ) {
given( $name ) {
#...
}
}
To go through many elements, you don’t need the given
. Let foreach
put the current element in $_
on its own. If you want to use smart matching, the current element has to be in $_
.
use 5.010;
foreach ( @names ) { # don't use a named variable!
when( /fred/i ) { say 'Name has fred in it'; continue }
when( /^Fred/ ) { say 'Name starts with Fred'; continue }
when( 'Fred' ) { say 'Name is Fred'; }
default { say "I don't see a Fred" }
}
If you are going to go through several names though, you probably want to see which name you’re working on. You can put other statements in the foreach
block, such as a say
statement:
use 5.010;
foreach ( @names ) { # don't use a named variable!
say "\nProcessing $_";
when( /fred/i ) { say 'Name has fred in it'; continue }
when( /^Fred/ ) { say 'Name starts with Fred'; continue }
when( 'Fred' ) { say 'Name is Fred'; }
default { say "I don't see a Fred" }
}
You can even put extra statements between the whens, such as putting a debugging
statement right before the default
(which you can also do with given
):
use 5.010;
foreach ( @names ) { # don't use a named variable!
say "\nProcessing $_";
when( /fred/i ) { say 'Name has fred in it'; continue }
when( /^Fred/ ) { say 'Name starts with Fred'; continue }
when( 'Fred' ) { say 'Name is Fred'; }
say "Moving on to default...";
default { say "I don't see a Fred" }
}