|Perl: the Markov chain saw|
Precedence for Idiotsby Melly (Hermit)
|on Dec 01, 2006 at 14:11 UTC||Need Help??|
Please note that this tutorial is undergoing revision and expansion, so the comments that follow it may apply to an earlier version. This version is dated: 5-Dec-2006
The Basics - Getting Your Sums Right
If, like me, you don't come from a comp-sci background, then precedence-awareness of operators probably only goes as far as knowing that 2+3*4 means 2+(3*4), and that if you want (2+3)*4, then you'd better damn well say so.
Beyond that, 5-1+2 might have you scratching your head - which has precedence - the '-' or the '+'? The answer is 'whichever comes first' - they have equal precedence but left associativity, so 5-1+2 is (5-1)+2, but 5+1-2 is (5+1)-2 (although you'll have fun proving that last one).
... and it's worth mentioning for the comp-sci challenged that left-associativity means "for a sequence of operators of equal precedence, give the left-most operator the precedence". Right-associativity means the reverse. For example, ** (the 'to-the-power-of' operator) has right-associativity, so 2**3**4 is 2**(2**3), not (2**2)**3.
So far, it's all pretty straight-forward. Whether or not you know what the rules for precedence are for the basic maths operators, you are aware that they exist and need to exist, and, if in doubt, or if you just want to make things clearer for yourself or the code-maintainer, you can always use brackets to make the order of operations explicit.
First Among Equals
So far, so good - that is until we get to the numeric-equality test, '==' and the assignment operator, '='.
The first thing to note (or at least remember) about these is that don't really have anything in common with each other. Nor do either have any strict equivalent in maths (unlike, say, '*' and '/', etc.).
It may be tempting to think otherwise, since $x = 2*4 (Perl) seems to behave a bit like X = 2 x 4 (maths). However, since we can use '=' to assign just about anything to $x, including "hello world", it really doesn't have anything to do with numbers.
In Perl, '==', and its evil-twin, '!=', are perhaps a bit closer to the maths-class meaning of '=', since all are associated with the numeric equality of the calculations on either side - however, in maths if the two sides don't match the operator, then you've probably made a mistake, whereas in Perl if the two sides don't match the operator, then you've just performed a valid test.
Nevertheless, the notion of precedence for these operators is somewhat confusing - if precedence is important, does that mean that we have to write ($x+$y) == (12/3) to avoid something like $x+($y == 12/3) happening? And what would that mean anyway?
By and large, you don't need to worry. Both '=' and '==' have such low precedence that they will almost always behave as you expect (and certainly as far as any maths-based functions go), without any need for parenthesis.
However, there are some traps when we start combining '==' and '=' with the various logical operators, such as 'and' and 'or', and their alternatives, '&&' and '¦¦', as these do have lower precedence.
For example, (5 or 2 == 12) doesn't mean "does 5 or 2 equal 12?" (which would be false), instead it translates to 5 or (2 == 12), or "if 5 is true or if 2 equals 12" (which is true - 5 is a 'true' value).
To add to the confusion, '&&' and '¦¦' have a higher precedence than '=', whereas 'and' and 'or' have a lower precedence. This means that $x = 4==5 ¦¦ 5==5 has quite a different meaning than $x = 4==5 or 5==5 - the first will set $x to 1 ('true') if either 4 or 5 is equal to 5, and will set $x to false if they are not. The second version will set $x to true or false purely on the basis of whether 4 is equal to 5 (and will go on to check whether 5 is equal to 5 if it fails to set $x to a value).
Below is a short table that will hopefully make all of this a little clearer.
The real lesson here is that when you start mixing '==' or '=' with any logical operators, get into the habit of using parenthesis... and just to rub that in, let's take a look at another logical operator, the slightly obscure, but extremely useful '?:' - and a particular trap you can fall into due to making unwarranted assumptions about the behavior of '='.
?: - If If/Else fails...
The '?:' operator is probably the least-known operator, so let's take a quick look at what it does.
The basic syntax is: <test>?<value to return if test is true>:<value to return if test is false>
Now, the "?:" construct is very useful - basically, it means that we can replace the following code:
Which is all well and good - unless you make the mistake of writing:
If you run the above code, you will find that, whatever value you assign to $x, you are always told that, apparently, $x was false (i.e. $y is set to 0).
So how did that happen, why was it confusing (IMHO), and what can you do about it?
Well, to illustrate what happened, let's write an alternative version that doesn't exhibit the problem, but looks pretty much identical (using a reg-ex substitution instead of '='):
This time, we get the result we expect. So what happened in the bad version that didn't happen here? Well the first thing to notice in the operator-precedence table is that '=~' has a higher precedence than '?:', but '=' has a lower precedence. So what? All that means, presumably, is that we decide on the truth or falsehood of our initial condition before we assign any value to $y (which sounds like a good thing).
Well... no. What precedence conceptually means in this context is "where is the boundary of our false expression?" and the answer is "it's when we hit an operator with a lower precedence than '?:'"
So $x ? $y=1 : $y=0 can be expressed as ($x ? $y=1 : $y)=0 - which, if $x is false, leads to ($y)=0 (correct), but if $x is true, leads to ($y=1)=0 (uh-oh - we did set $y to 1, but then immediately reset it to 0).
Now, when we replace a false expression such as $y=0 with $y=~s/.*/0/, the higher precedence of '=~' means that Perl evaluates this as:
which is probably what we (the comp-sci challenged) expected in the first example.
Bottom line, '?:' can benefit from parenthesis just as much as (2+3)*5 - here is the bad code made good:
As a small side-note, really we ought to be writing $x ? ($y=1) : ($y=0);, but Perl 'knows' that the function between '?' and ':' must be our 'true' function and is kind enough to add the virtual parenthesis for us...
...and, as noted before, we can avoid the need for parenthesis, and save a few key-strokes, by writing:
... which is really what we should have done in the first place - there is an Meditation discussing the use of '?:' at ?: = Obfuscation?.
A Final Word
This is not meant to be an exhaustive look at precedence and operators - I haven't mentioned the bit-wise operators for example. However, I hope I've covered the issues likely to fox the comp-sci challenged (basically, if you're using bit-wise operators, I assume you know what you're doing).
Also, I'm half-tempted (well, 25% tempted) to replace this tutorial with just the one sentence "USE LOTS OF PARENTHESIS" - it's certainly the bottom line. They will make your code more readable, and you will avoid most of the traps associated with precedence.
That said, don't go over the top:
is not really helping anyone....
Tom Melly, firstname.lastname@example.org