Why does short-circuit evaluation work when operator precedence says it shouldn't?
Asked Answered
W

3

8

In JavaScript and Java, the equals operator (== or ===) has a higher precedence than the OR operator (||). Yet both languages (JS, Java) support short-circuiting in if statements:

When we have if(true || anything()), anything() isn't evaluated.

You can also have the following expression: true || foo == getValue()) - for example in an output statement such as console.log(...);, or in an assignment.

Now, according to operator precedence, short-circuiting shouldn't happen, as === = == > || in terms of precedence. (In other words, the comparison should happen first, for which getValue() ought to be called, as the equality check has a higher precedence that the OR comparison.) But it does. getValue() isn't called (as can easily be checked by putting an output statement into its body).

Why (does short circuiting work when the operator precedence says it shouldn't)?
Or am I confusing matters?

Willetta answered 30/9, 2017 at 19:2 Comment(3)
Related: What are the rules for evaluation order in Java?Decompose
You have a contradiction in your question. You said "...the comparison should happen first... getValue() ought to be called... But it does." So part of your question implies that getValue() is not called, but this statement says that is is called. Which is it?Voleta
@Chloe: His "but it does" is referring to "short circuiting shouldn't happen", which balances with his concern that "getValue() ought to be called..." and his observation that "getValue() isn't called".Rompers
A
20

Or am I confusing matters?

You are. I think it's much simpler to think about precedence as grouping than ordering. It affects the order of evaluation, but only because it changes the grouping.

I don't know about Javascript for sure, but in Java operands are always evaluated in left-to-right order. The fact that == has higher precedence than || just means that

true || foo == getValue()

is evaluated as

true || (foo == getValue())

rather than

(true || foo) == getValue()

If you just think about precedence in that way, and then consider that evaluation is always left-to-right (so the left operand of || is always evaluated before the right operand, for example) then everything's simple - and getValue() is never evaluated due to short-circuiting.

To remove short-circuiting from the equation, consider this example instead:

A + B * C

... where A, B and C could just be variables, or they could be other expressions such as method calls. In Java, this is guaranteed to be evaluated as:

  • Evaluate A (and remember it for later)
  • Evaluate B
  • Evaluate C
  • Multiply the results of evaluating B and C
  • Add the result of evaluating A with the result of the multiplication

Note how even though * has higher precedence than +, A is still evaluated before either B or C. If you want to think of precedence in terms of ordering, note how the multiplication still happens before the addition - but it still fulfills the left-to-right evaluation order too.

Anguished answered 30/9, 2017 at 19:7 Comment(8)
While I totally get short-circuiting, I still fail to see why it works with the rules. Essentially, we have operand1 operatorA operand2 operatorB operand3 with operatorA's preceence being lower that operatorB's, so operator precedence should cause o2 oB o3 to be evaluated first. Instead (due to short circuiting) the interpreter sees operatorA is an OR and thus only evaluates operand1. That evaluates to true, so nothing else gets evaluated. Despite operator precedence stating it should be the other way round; the o2 oB o3 should run first.Willetta
@Christian: "so operator precedence should cause o2 oB o3 to be evaluated first" - no, it really doesn't. Operator precedence means it's grouped such that the operands of operatorA are "operand1" and "the result of evaluating operatorB". But operand1 is still evaluated first. That what I mean about it being more about grouping.Anguished
In fact, the left-to-right rule only applies to operands of the same order: 1 + 2 + 3 => 3 + 3 => 6 whereas 1 + 2 * 3 => 1 + 6 => 7, not 3 * 3 => 9, right? If so, we're back at square one, because the || has a lower order than the ==, yet evaluates first (i.e. we get 9, instead of 6)...Willetta
@Christian: No, that's associativity you're thinking of. See docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.7: "The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated." So the left-hand operand of || is evaluated before the right-hand operand is evaluated.Anguished
@Christian: You might find this blog post I wrote about the topic a while ago useful: codeblog.jonskeet.uk/2015/04/21/precedence-ordering-or-groupingAnguished
Thanks, will have a look. In the meantime, for all other non-native speakers out there, be aware that a bitwise operator like | is not the same as (but an example of) a binary operator (that works on two operands, as opposed to the ternary one, ? : that works with three), if I'm not mistaken. That nearly got me. Also just found out that Java calls braces "separators", not "operators" in the JLS: docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.12Willetta
I had to read Eric Lippert's answer to a question referred to by @pshermo, and your blog post, but now your answer makes sense. Thank you. To summarise what I understand: the operators determine what operands (or results) get grouped together, but the "subexpressions are evaluated left to right", and because of short-circuiting, it stops after evaluating the left-most to true (followed by ||).Willetta
This explanation was helpful, by the way I think it applies in the same way to JavaScript as well.Whitlow
V
2

According to the language specification, https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.24

At run time, the left-hand operand expression is evaluated first; if the result has type Boolean, it is subjected to unboxing conversion (§5.1.8).

If the resulting value is true, the value of the conditional-or expression is true and the right-hand operand expression is not evaluated.

So if you have a || b==c, it is not interpreted as (a || b) == c, because || has lower precedence, as you found in the tutorial. Instead, it is interpreted as a || (b==c). Now since a is the left side of ||, it is evaluated first.

Voleta answered 30/9, 2017 at 19:53 Comment(1)
I find a || (b==c) not any less confusing, as the brackets have an even higher precedence in both languages, for Java see docs.oracle.com/javaee/6/tutorial/doc/bnaik.htmlWilletta
T
1

There is no operator precedence in this case. What you are questioning is like in f(callback) statement the callback function being evaluated even before f. This can not happen.

On the other hand, in JS the || is one of the few places where you can watch laziness at show. The == operand (think as if it is an infix function like in fully functional languages) takes two arguments and the one on the left gets evaluated first. If it resolves to true the second argument doesn't even get evaluated.

Torrence answered 30/9, 2017 at 20:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.