25

The logical AND and OR operators are the only lazy operators in JavaScript along with the ternary conditional operator. They are tested for short-circuit evaluation using the following rules:

false && anything === false
true || anything === true

This is the same way it is implemented in Haskell:

(&&) :: Bool -> Bool -> Bool
False && _ = False
True  && x = x

(||) :: Bool -> Bool -> Bool
True  || _ = True
False || x = x

However according to MDN logical operators in JavaScript are left associative. This is counter intuitive. In my humble opinion they should be right associative. Haskell does the right thing. Logical operators in Haskell are right associative:

infixr 3 &&
infixr 2 ||

Consider the following expression in Haskell:

False && True && True && True

Because && is right associative in Haskell the above expression is equivalent to:

False && (True && (True && True))

Hence it doesn't matter what the expression (True && (True && True)) evaluates to. Because of the first False the entire expression is reduced to False in a single step.

Now consider what would happen if && was left associative. The expression would be equivalent to:

((False && True) && True) && True

It would now take 3 reductions to evaluate the entire expression:

((False && True) && True) && True
(False && True) && True
False && True
False

As you can see it makes more sense for logical operators to be right associative. This brings me to my actual question:

Why are logical operators in JavaScript left associative? What does the ECMAScript specification have to say about this? Are logical operators in JavaScript actually right associative? Does the MDN docs have incorrect information about the associativity of logical operators?


Edit: According to the specification logical operators are left associative:

LogicalANDExpression = BitwiseORExpression
                     | LogicalANDExpression && BitwiseORExpression

LogicalORExpression = LogicalANDExpression
                    | LogicalORExpression || LogicalANDExpression
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 4
    The result, including short-circuiting, is the same either way, isn't it? – Barmar Dec 15 '13 at 06:34
  • The result will always be the same because of the way the operators are defined. However left associative logical operators would require more evaluations/reductions as described in the question. – Aadit M Shah Dec 15 '13 at 06:36
  • @AaditMShah it's best to regard logical operators as variadic and ignore formal associativity. That's what most implementations do, I assume – John Dvorak Dec 15 '13 at 06:39
  • @JanDvorak How would you regard logical operators as variadic? They aren't functions. I doubt that's what most implementations do. – Aadit M Shah Dec 15 '13 at 06:41
  • The compiler should be able to convert it into efficient code that doesn't do any more evaluations than are necessary. – Barmar Dec 15 '13 at 06:43
  • @Barmar In that case wouldn't you agree that the compiler would treat the logical AND and OR operators to be right associative so as to generate an efficient parse tree? – Aadit M Shah Dec 15 '13 at 06:44
  • 3
    If the associativity doesn't make any difference in the result, the compiler can do it either way. The authors of the specification probably didn't worry about this since it doesn't matter. – Barmar Dec 15 '13 at 06:46
  • @Barmar So the associativity of logical operators is left for the implementation to decide? What does the ECMAScript specification have to say about this? – Aadit M Shah Dec 15 '13 at 06:48
  • @Aadit http://es5.github.io/#x11.11 – Moritz Roessler Dec 15 '13 at 06:49
  • You can find it here: http://www.ecma-international.org/ecma-262/5.1/#sec-11.11 It's described in terms of grammar productions rather than associativity, and I'm not sure how it converts. – Barmar Dec 15 '13 at 06:53
  • @Barmar It's left associative according to the specification. Hence MDN was right. – Aadit M Shah Dec 15 '13 at 06:57
  • 1
    From the MDN table you linked to, it looks like they simply made all binary operators left-associative, except for assignments. In most cases, the choice is arbitrary because the operations are commutative. – Barmar Dec 15 '13 at 06:57
  • Short-circuiting operators aren't commutative, but as long as you short-circuit from left-to-right, it still doesn't matter how you associate them. – Barmar Dec 15 '13 at 07:00
  • @Barmar you mean associative, right? Lot of them aren't commutative, even if most of those that aren't are anti-commutative. – John Dvorak Dec 15 '13 at 07:00
  • 1
    I think I mean that they're associative _and_ commutative. Ignoring the short-circuiting, `a && b` is equivalent to `b && a`. Most commutative operators are also associative, but I found this: http://unspecified.wordpress.com/2008/12/28/commutative-but-not-associative/ – Barmar Dec 15 '13 at 07:03
  • 1
    @Barmar `&&` and `||` are only commutative for boolean arguments. Sure, it's the primary use-case (or at least the original intended primary use-case), but still.. – John Dvorak Dec 15 '13 at 07:07
  • That's why I said "ignoring the short-circuiting". – Barmar Dec 15 '13 at 07:11
  • Well, in your example if last value is `False` and first value is `True` then left association will be efficient as compared to right association. – Ankur Dec 15 '13 at 07:24
  • 4
    @Ankur No, it wouldn't. Because of the way logical AND and OR is defined the first operand is always evaluated and the second operand is only evaluated when the first evaluation doesn't short-circuit. You cannot evaluate the second operand before evaluating the first operand. Hence even if the last operand is `False` you'll still need to evaluate the first operand, which in turn would need to evaluate its first operand and so on. – Aadit M Shah Dec 15 '13 at 07:45
  • Following your example, wouldn't `True && True && True && False` be more efficient if left associative? – dhc Dec 15 '13 at 10:56
  • 1
    @dhc No, It wouldn't. The expression `True && True && True && False` would be explicitly parenthesized as `((True && True) && True) && False`. However because of the way `&&` is defined you always have to evaluate the first operand before the second operand. Hence it would reduce to `False` in 3 reductions as follows: `((True && True) && True) && False` would reduce to `(True && True) && False` which would reduce to `True && False` which would reduce to `False`. This behavior is demonstrated in the following fiddle: http://jsfiddle.net/Yp5GN Note that every term in the expression is evaluated. – Aadit M Shah Dec 15 '13 at 11:04
  • Ah, so it's left associative, but still evaluated left-to-right. Why would '&&' be defined that way if it's commutative? – dhc Dec 15 '13 at 11:30
  • @AaditMShah, your comment about reductions is not relevant, since on sensible interpreters JavaScript is precompiled. I don't see how different bytecode could be generated depending on associativity of `&&`, `||`. Thus it really doesn't matter. – Karolis Juodelė Dec 15 '13 at 12:02
  • 1
    @dhc It is defined that way so that you may use them as [guard and default operators](http://seanmonstar.com/post/707078771/guard-and-default-operators "Guard and Default Operators of JavaScript - seanmonstar") respectively. – Aadit M Shah Dec 15 '13 at 14:24
  • 1
    @KarolisJuodelė I don't claim to know about the internals of a JavaScript engine. The generated code may be optimized. Nevertheless because of the way they are defined in my humble opinion it seems more intuitive to make the logical AND and OR operators right associative. The associativity of operators indeed makes a difference in lazy languages like Haskell where evaluations are simply graph reductions and hence I suspect that it might also make a difference in this case as the logical operators have short-circuit evaluation. Would you show me the relevant compiler code to back your claim? =) – Aadit M Shah Dec 15 '13 at 14:41
  • @AaditMShah optimization is a fancy word to use. A short circuiting `if (A || B)` is going to be implemented `run A`, `conditional-jump`, `run B`, `conditional-jump`. That's how C did it and that's how JS will do it (though I don't know of a way to look at the latter). What you're doing is mistaking theoretical models for reality. – Karolis Juodelė Dec 15 '13 at 15:50
  • It continues to short circuit, despite its associativity : `function sideEffect() {console.log('run');return true}; false && true && sideEffect() === false /* with nothing printed*/` – kybernetikos Dec 16 '13 at 00:10
  • 1
    @kybernetikos The result will always be the same irrespective of the associativity. However the number of reductions for left associative logical operators would be more. For example `false && true && sideEffect()` is equivalent to `(false && true) && sideEffect()`. This would reduce to `false && sideEffect()` and then reduce to `false`. Hence we have two reductions. However if the logical operators were right associative it would only take a single reduction. The expression `false && (true && sideEffect())` would reduce to `false` in a single reduction. Hence they should be right associative. – Aadit M Shah Dec 16 '13 at 02:03
  • You can check this easy: `(function(){ console.log(1); })() && (function(){ console.log(2) })() && (function(){ console.log(3) })()` – kangax Dec 16 '13 at 23:34

3 Answers3

15

For any decent compiler the chosen associativity of these operators are pretty much irrelevant, and the outputted code will be the same regardless. Yes, the parse tree is different, but the emitted code doesn't need to be.

In all of the languages of the C family that I know of (to which Javascript also belongs), the logical operators are left associative. So the real question becomes, why do C-like languages define logical operators as left-associative? Since the chosen associativity is irrelevant (in terms of semantic, and in terms of efficiency), my suspicion is that the most "natural" (as in "what the majority of the other operators use") associativity was chosen, although I don't have any sources to back up my claims. Other possible explanation is that left associative operators take less stack space to parse using an LALR parser (which is not a big concern nowadays, but probably was back when C appeared).

Pedro Rodrigues
  • 1,732
  • 11
  • 17
  • 1
    Evaluation of boolean expressions in C-like languages behave vaguely like foldl', which is fine because they are always strict anyway. – misterbee Dec 28 '13 at 18:09
5

Consider this code:

console.log(   true || (false && true) );   // right associative (implicit)
console.log(  (true || false) && true  );   // left associative (Javascript)

Both of those examples do in fact return the same result, but that is not the reason to worry about operator associativity. The reason it's relevant here is because of the unique way that logical operators determine their outcomes. Even though all permutations ultimately make the same final conclusion, the order of the calculations change, and that can have big implications for your code.

So, now consider this:

    var name = "";

    // ----------------------------------------
    // Right associative
    // ----------------------------------------
    name = "albert einstein";
    console.log("RIGHT : %s : %s", true || (false && updateName()), name);

    // ----------------------------------------
    // Left Associative
    // ----------------------------------------
    name = "albert einstein";
    console.log("LEFT  : %s : %s", (true || false) && updateName(), name);

    function updateName() {
        name = "charles manson";
        return true;
    }

The output is:

    RIGHT : true : albert einstein
    LEFT  : true : charles manson

Both expressions return true, but only the left associated version had to call updateName() in order to return an answer. The right associated version is different. It only evaluates the first argument in (false && updateName()) because the second argument can't change the false into a true.

Remember these two points:

  • Operator Precedence describes the nesting order of compound expressions of different operator types.
  • Operator Associativity describes the nesting order of compound expressions with the same operator precedence.

Notice that neither of the two points above change the way an individual expression is interpreted, only the way compound expressions are interpreted. Associativity happens at a higher level.

Operator associativity, as well as operator precedence, have enormous impact in how a language behaves. Understanding what those differences are is critical to being able to use and quickly grasp the functional differences in diverse programming languages. You definitely get a thumbs up from me for your question and I hope this clarifies things a little bit. Take care.

drankin2112
  • 4,715
  • 1
  • 15
  • 20
  • 1
    What do you mean by "...the logical operators in JavaScript are definitely right associative"? The grammar in the specification defines the logical operators as left associative. The interpreter example code shows short-circuit evaluation, it doesn't show associativity at all. – Pedro Rodrigues Dec 24 '13 at 13:34
  • 1
    You are right. I had remembered it backwards. My main point was to address everyone's focus on the expression outcomes as opposed to their impact. Thank you. Post updated. – drankin2112 Dec 24 '13 at 16:54
  • This is probably why JavaScript defines || and && as different operator "types" so that they are resolved with precedence, not associativity. – Aleksandr Dubinsky Dec 26 '13 at 11:25
  • Thank you, this answer helped me with my efforts to grasp the essence of what associativity means. – thewoolleyman Jul 16 '16 at 18:22
  • 1
    This answer has nothing to do with the original question, which is about the left associativity of each of the individual logical operators, and not about expressions mixing both (which, as this very answer explains, is resolved by precedence). – Tom Dec 28 '19 at 12:22
-4

Simple answer: Imagine var i = null; if (i == null || i.getSomeValue()) ... When not being left associative, the second test would be evaluated first giving you an exception.

Peter
  • 1,769
  • 1
  • 14
  • 18
  • 1
    That's not what the question is about. Associativity only matters in expressions with multiple operators in a chain, like `if (i==null || i.isBad() || i.getSomeValue())`, which may be parsed either as `(i==null || i.isBad()) || i.getSomeValue()` (left-associative) or `i==null || (i.isBad() || i.getSomeValue())` (right-associative). – leftaroundabout Dec 24 '13 at 00:18
  • Associativity doesn't decide which operand is evaluated first. They way the operator is defined decides which operand is evaluated first. Read the question carefully. – Aadit M Shah Dec 24 '13 at 03:48