5

Given the code:

my $x = 1;

$x = $x * 5 * ($x += 5);

I would expect $x to be 180:

$x = $x * 5 * ($x += 5); #$x = 1
$x = $x * 5 * 6;         #$x = 6
$x = 30 * 6;
$x = 180;
180;

But instead it is 30; however, if I change the ordering of the terms:

$x = ($x += 5) * $x * 5;

I do get 180. The reason I am confused is that perldoc perlop says very plainly:

A TERM has the highest precedence in Perl. They include variables, quote and quote-like operators, any expression in parentheses, and any function whose arguments are parenthesized.

Since ($x += 5) is in parentheses, it should be a term, and therefore executed first, regardless of the ordering of the expression.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
Chas. Owens
  • 64,182
  • 22
  • 135
  • 226
  • 4
    You know, having learned C first, I never do stuff like this and expect it to work the way I think it ought to work: http://c-faq.com/expr/index.html ;-) – Sinan Ünür Nov 05 '09 at 17:51
  • 3
    I too came from ANSI C, and yeah, this isn't code I would write, it is me trying to make sure I understand precedence in Perl before I explain it to someone else. Using side-effects like this is a major no-no, but still legal in Perl. In ANSI C, if you had more than one side-effect in an expression the results were undefined, in Perl side-effects are better defined, but still a really bad idea. – Chas. Owens Nov 05 '09 at 18:12
  • I once made a lovely post about this to comp.lang.perl.misc, and have never been able to find it again. – brian d foy Nov 06 '09 at 18:42
  • My advice: don't do this. Perl allows it, but it's a serious readability problem. You do not want to have to work on code where you have to carefully parse an expression to figure out what it means. – David Thornley Nov 06 '09 at 19:23
  • 2
    @David Thornley It isn't a matter of doing it. It is a matter of understanding it. I am writing an article that involves precedence and associativity of operators and I don't want to state anything that is false. This means I need to understand what Perl does much better than is normally necessary; hence the bad, but legal, code as I look for inconsistencies in my understanding. – Chas. Owens Nov 06 '09 at 20:39

4 Answers4

16

The act of typing out the question yielded the answer to me: terms have the highest precedence. That means that the $x in the first chunk of code is evaluated and yields 1, then 5 is evaluated and yields 5, then ($x += 5) is evaluate and yields 6 (with a side-effect of setting $x to 6):

$x = $x * 5 * ($x += 5);
address of $x = $x * 5 * ($x += 5); #evaluate $x as an lvalue
address of $x = 1 * 5 * ($x += 5);  #evaluate $x as an rvalue
address of $x = 1 * 5 * ($x += 5);  #evaluate 5
address of $x = 1 * 5 * 6;          #evaluate ($x += 5), $x is now 6
address of $x = 1 * 5 * 6;          #evaluate 1 * 5
address of $x = 5 * 6;              #evaluate 1 * 5
address of $x = 30;                 #evaluate 5 * 6
30;                                 #evaluate address of $x = 30

Similarly, the second example reduces like this:

$x = ($x += 5) * $x * 5; 
address of $x = ($x += 5) * $x * 5; #evaluate $x as an lvalue
address of $x = 6 * $x * 5;         #evaluate ($x += 5), $x is now 6
address of $x = 6 * 6 * 5;          #evaluate $x as an rvalue
address of $x = 6 * 6 * 5;          #evaluate 5
address of $x = 36 * 5;             #evaluate 6 * 6
address of $x = 180;                #evaluate 36 * 5
180;                                #evaluate $x = 180
Chas. Owens
  • 64,182
  • 22
  • 135
  • 226
  • 7
    Correct. What was tripping you up wasn't precedence but order of evaluation. The `($x += 5)` isn't evaluated until after the first multiplication takes place. The parentheses only serve to ensure that the `+=` happens before the (second) multiplication. In this case, that prevents a syntax error because multiplication has a higher precedence than assignment and the result of multiplication isn't a valid lvalue. – Michael Carman Nov 05 '09 at 20:23
10

Whenever I have confusion about stuff like this I first pull out perldoc perlop, and then if I'm still not sure, or want to see how a particular block of code will get executed, I use B::Deparse:

perl -MO=Deparse,-p,-q,-sC
my $x = 1;
$x = $x * 5 * ($x += 5);

^D

gives:

(my $x = 1);
($x = (($x * 5) * ($x += 5)));
- syntax OK

So substituting values at each stage gives:

($x = (($x * 5) * ($x += 5)));
($x = ((1 * 5) * ($x += 5)));
($x = ((5) * (6))); # and side-effect: $x is now 6
($x = (5 * 6));
($x = (30));
($x = 30);
$x = 30;

So the fact that $x was temporarily set to 6 doesn't really affect anything, because the earlier value (1) was already substituted into the expression, and by the end of the expression it is now 30.

Ether
  • 53,118
  • 13
  • 86
  • 159
  • Interesting. But it brings up the question where the parens around $x*5 come from. – innaM Nov 05 '09 at 18:52
  • 4
    `*` is a binary operator that evaluates left-to-right, so `$x * $y * $z` would evaluate as `(($x * $y) * $z)`. – Ether Nov 05 '09 at 19:03
  • Ether, *evaluating* left to right isn't the same as *associating* left to right. Even if `*` associated right to left, it could still be evaluated left to right: first evaluate $x and store its value in a temporary; then evaluate $y, then $z; next multiply them and multiply the result with the saved temporary value from $x. Precedence doesn't determine evaluation order, unless you can find some documentation that says otherwise for Perl. (Java, for instance, is defined to evaluate from left to right, even if a right-hand operator has higher precedence.) – Rob Kennedy Nov 05 '09 at 23:28
  • (What I mean to say is that Java evaluates *operands* from left to right. *Operators* are evaluated according to their precedence rules.) – Rob Kennedy Nov 05 '09 at 23:36
  • @Rob: yes, you're absolutely right. Thanks for correcting my muddy wording! Clarity of thought means clarity of code.. :) – Ether Nov 05 '09 at 23:59
4

$x by itself is also a TERM. Since it is encountered first (in your first example), it is evaluated first.

mob
  • 117,087
  • 18
  • 149
  • 283
2

The associativity of the * operator is leftward, so the left most term is always evaluated before the right most term. Other operators, such as ** are right associative and would have evaluated ($x += 5) before the rest of the statement.

Ven'Tatsu
  • 3,565
  • 16
  • 18