Perl has a complicated precedence ordering; it helps to be able to look it up quickly (fortunately, it's the first thing out of perldoc perlop). We reproduce that table here (Table 4-1) because we'll be referring to it.
Associativity | Operators |
---|---|
Left | Terms and list operators (leftward) |
Left | -> |
Nonassoc | ++ -- |
Right | ** |
Right | ! ~ and unary + and - |
Left | =~ !~ |
Left | * / % x |
Left | + - . |
Left | << >> |
Nonassoc | Named unary and file test operators |
Nonassoc | < > <= >= lt gt le ge |
Nonassoc | == != <=> eq ne cmp |
Left | & |
Left | | ^ |
Left | && |
Left | || |
Nonassoc | .. … |
Right | ?: |
Right | = op= |
Left | , => |
Nonassoc | List operators (rightward) |
Right | not |
Left | and |
Left | or xor |
Associativity tells you what order to evaluate multiple operators of the same precedence appearing together in an expression. So the arithmetic expression 3 - 4 + 5 is evaluated as (3 - 4) + 5 because while - and + have the same precedence, they are left-associative, meaning that left-most elements take precedence over right-most elements. “Nonassoc” means that the operators don't associate; in other words, you can't have more than one in a row without parentheses to create a valid meaning. So while some languages let you write[5]
[5] One of the first languages Peter learned was BCPL, which allowed this.
if (0 <= $percent <= 100)
Perl isn't one of them.[6]
[6] Yet. It's been mooted for Perl 6.
As the perlop manual page says, “Operators borrowed from C keep the same precedence relationship with each other, even where C's precedence is slightly screwy.” It turns out that some of the precedence rules for C were chosen to match those of its predecessor B, even where those were slightly screwy. (Backward compatibility has its pitfalls.)
So most of the precedence gotchas in C are inherited by Perl. For instance, if you're checking a variable that contains a selection of flags logically ORed together against a candidate (as we will do in Chapter 5), the line
warn "Database opened " if ($opt_D & $DB_LOG != 0)
has an unfortunate flaw, because it is evaluated as
warn "Database opened " if ($opt_D & ($DB_LOG != 0))
So if, for instance, $opt_D was 6, and $DB_LOG was 2, the warn statement would be incorrectly omitted since 2 != 0 evaluates to 1, and 1 & 6 is 0, which is false.
The cure lies in simplicity:
warn "Database opened " if ($opt_D & $DB_LOG)
at the cost of infinitesimal clarity.
Here's another example of precedence combined with optional parentheses around function arguments getting us into trouble:
vec $v, $offset, 32 = 1; # Wrong
The programmer who thought that since the prototype for vec is $$$ that the processing of its arguments would terminate after 32 failed to realize that = has a higher precedence than the commas separating list arguments. Therefore Perl parses it as:
vec($v, $offset, (32 = 1));
and emits the error
Can't modify constant item in scalar assignment.
What other precedence pitfalls pursue Perl programmers? A few follow.
Let's say that you want to check whether the next element of an array matches a regular expression, and you write
if (shift @marsupials =~ /platypus|wallaby|(kanga)?roo|joey/) ...
This results in a warning (with -w) and an error:
Applying pattern match to @array will act on scalar(@array).. Type of arg 1 to shift must be array (not pattern match)...
Reason: the binding operator =~ is above “named unary operators” in Table 4-1. So it tries to bind the regex to @marsupials (which must put the array in scalar context, hence the warning), and then shift the result of the regex match (which results in the error).
Fortunately, there are so many ways of coding this right that this is an unlikely pitfall to encounter by accident. Like all precedence misunderstandings, inserting parentheses is one way to fix it:
if (shift(@marsupials) =~ /platypus|wallaby|(kanga)?roo|joey/)
Use parentheses when in doubt about precedence; they won't hurt. |
Suppose you have a hash %h and you want to find out how many elements are in it, so you type:
print "Number of elements: " . keys %h . " ";
reasoning with impeccable logic that the dots will force keys into scalar context and therefore return the number of keys in %h instead of the list of them. However, Perl responds with
Type of arg 1 to keys must be hash (not concatenation)
A quick glance at Table 4-1 shows us that “named unary operators” rank below the . operator in precedence; therefore Perl parses the statement as
print "Number of elements: " . keys (%h." ");
which, unsurprisingly, makes no more sense to Perl than it does to us. We can fix it easily enough by leaving the first dot in and turning the second one into a comma:
print "Number of elements: " . keys %h, " ";
(This comma is evaluated as the low-precedence “list operators (rightward)” element from Table 4-1.) We can generalize this to all the other named unary operators (functions prototyped to take a single argument), of course. Peruse the perlfunc manual page to find out what those are.