Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Precedence for Idiots

by Melly (Hermit)
on Dec 01, 2006 at 14:11 UTC ( #587193=perltutorial: print w/ replies, xml ) Need Help??

Introduction

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.

Logical Questions

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.

FunctionMeaning$x is now..
$x = 5 == 6 or 5 == 5($x = (5 == 6)) or ($x = (5 == 5))FALSE
$x = (5 == 6 or 5 == 5)$x = ((5 == 6) or (5 == 5))TRUE
$x = 5 == 6 5 == 5$x = ((5 == 6) (5 == 5))TRUE
($x = 5 == 6) 5 == 5($x = 5 == 6) 5 == 5FALSE
$x = 5 6 == 6$x = (5 (6 == 6))5
$x = (5 6) == 6$x = ((5 6) == 6)TRUE
$x = 5 or 6 == 6($x = 5) ($x = (6 == 6))5
$x = 1 == 2 && 3$x = (1 == 2) && $x = 33
$x = 1 == 2 3$x = (1 == 2) $x = 3FALSE

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:

if($x){ $y=1; } else{ $y=0; }

with:

$y = $x ? 1 : 0;

Which is all well and good - unless you make the mistake of writing:

$x ? $y=1 : $y=0;

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 '='):

$x ? $y=1 : $y=~s/.*/0/;

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:

$x ? $y=1 : ($y=~s/.*/0/)

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:

$x ? $y=1 : ($y=0);

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:

$y = $x ? 1 : 0;

... 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:

$x = ((((((1 * 2) * 3) * (4 ** 2)) * 5) * 6) * 7)

is not really helping anyone....

Tom Melly, pm@tomandlu.co.uk

Comment on Precedence for Idiots
Select or Download Code
Re: Precedence for Idiots
by BrowserUk (Pope) on Dec 01, 2006 at 14:37 UTC

    Excellent. Tutorials written by people who have just encountered and resolved an issue, are usually so much more lucid than those written by 'experts'.

    They may not cover all of the subject, or delve into the deep dark recesses as the expert might, but they are all the better for that. Experts have usually long forgotten where the dragons are, and end up re-writing reference manuals instead of tutorials.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Precedence for Idiots
by Tanktalus (Canon) on Dec 01, 2006 at 15:22 UTC

    What I've attempted a few times, long ago before readability became important to me, is this:

    $x ? $y = 1 : $z = 1;
    That doesn't work, either. Parenthesis can help, of course. But that's not the right answer. The right answer is:
    if ($x) { $y = 1; } else { $z = 1; }
    Why? Because it's readable. If you're golfing, then maybe that's not what you want to hear. But for anything that needs maintenance, please consider this ;-)

    (I'll echo BrowserUk's comments, though - a tutorial that moves someone from "initiate" to "novice" is sometimes more useful than moving someone from "novice" to "advanced"...)

    Update: I stand corrected. jdporter's reply seems much more readable. <cough><cough>

    *boggle*

    (Yeah, I know, it was meant as a joke after my maintenance comment. At least, I *hope* it was a joke... ;->)

      f00f.

      ${ $x ? \$y : \$z } = 1; ${ (\($y,$z))[!$x] } = 1;
      We're building the house of the future together.

      Hmm... I half agree with you. A week ago, any code containing ?: would have confused the hell out of me, but purely because I'd never used it or made myself familiar with what it did (or, to be honest, even knew it existed).

      However, I think one has to make a distinction between "this is obfuscated because I don't recognise the operator" and "this is obfuscated because I don't understand what the use of this familiar operator is going to produce under these conditions".

      IMHO, once the basics of ?: are understood, then one could argue that one line of code to assign a value to a variable, rather than 6 is no more or less obfuscated, but may make the code more readable purely on the basis of being shorter... still, this might make an interesting discussion point in meditations, so I'll raise it there, and edit my tutorial accordingly according to the consensus...

      map{$a=1-$_/10;map{$d=$a;$e=$b=$_/20-2;map{($d,$e)=(2*$d*$e+$a,$e**2 -$d**2+$b);$c=$d**2+$e**2>4?$d=8:_}1..50;print$c}0..59;print$/}0..20
      Tom Melly, pm@tomandlu.co.uk
Re: Precedence for Idiots
by mreece (Friar) on Dec 01, 2006 at 15:28 UTC
    a little whitespace around your operators would make this more readable!

    personally, i don't think a precedence tutorial can be complete without touching on the difference between && and || vs and and or.

    this bug bit me yesterday:

    return $this and $that and is_ok($this, $that);
Re: Precedence for Idiots
by virtualsue (Vicar) on Dec 01, 2006 at 16:16 UTC
    At first glance, this node appeared to revolve around avoiding problems that mainly exist for a developer if the familar, self-documenting and more maintainable construct "if {} else {}" is discarded and replaced by "?:". It doesn't seem to be about operator precedence in general.

    I'm not overjoyed by the examples, either.

    $x?$y=1:$y=~s/.*/0/;
    What?? How about
    my $y = 1; $y = 0 if (!$x);
    or, if you actually need a regular expression:
    my $y = "a big string of something"; if (!$x) { $y =~ s/somebigoldregex/etc/; }
    The above are more readable and make it fairly obvious what is going on. These attributes are very important. If someone (might even be you) has to debug a problem involving your code at 1 am on 5 cups of bad coffee, they're going to understand it faster and will be less likely to want to murder you when they're done. Even better, by writing clearer code you reduce the chances that you or somebody else will need to debug your code in the wee hours in the first place.

    This may seem like a harsh response, but I gave you a ++ for your effort and enthusiasm. I would however like to propose that this node comes out of Tutorials, but only until it is reworked into a straightforward "how to use ?:" tutorial which does not even remotely look like it is encouraging the replacement of if/else statements in general. There are (a few) places where the ternary is useful and good, and it's fine to help people write them correctly. Coming up with more realistic examples would be nice. In addition, the title should be changed to something more meaningful.

      I agree with 1.5 of your main 2 points - I must admit that my personal jury is still out on ?: vs. if/else/etc. - and I definitely agree that the tutorial needs expanding to include, e.g., precedence for 'and' and '&&', etc. if the title is to be appropriate.

      Thanks for the comments - not harsh at all.

      map{$a=1-$_/10;map{$d=$a;$e=$b=$_/20-2;map{($d,$e)=(2*$d*$e+$a,$e**2 -$d**2+$b);$c=$d**2+$e**2>4?$d=8:_}1..50;print$c}0..59;print$/}0..20
      Tom Melly, pm@tomandlu.co.uk
Re: Precedence for Idiots
by throop (Chaplain) on Dec 01, 2006 at 21:52 UTC
    Thanks, Tom. I particularly appreciated the discussion and examples of left and right associativity. Other discussions of precedence too often give it short shrift. Your $a=$b=10 example was just right.
Re: Precedence for Idiots
by calin (Deacon) on Dec 04, 2006 at 21:59 UTC

    Two comments:

    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 3*2+1 means (3*2)+1, and that if you want 3*(2+1), then you'd better damn well say so.

    I think putting the addition first (to the left) in your example illustrates the concept of precedence awareness better. Like: 1+3*2 is actually 1+(3*2) and not (1+3)*2.

    Second comment: A short paragraph about the C-istic awkward precedence of some operators might be helpful to beginners without a C background. $x == $y & $z means ($x == $y) & $z. Enabling warnings catches this btw.

      Good points - I'll edit the article to include them.

      map{$a=1-$_/10;map{$d=$a;$e=$b=$_/20-2;map{($d,$e)=(2*$d*$e+$a,$e**2 -$d**2+$b);$c=$d**2+$e**2>4?$d=8:_}1..50;print$c}0..59;print$/}0..20
      Tom Melly, pm@tomandlu.co.uk

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perltutorial [id://587193]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (12)
As of 2014-09-18 18:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (120 votes), past polls