4.2. The Hall of the Precedence

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.

Table 4-1. Perl Operator Precedence and Associativity
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.

4.2.1. Regex Binding

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.


4.2.2. Arithmetic on keys

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset