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
a && b
is equivalent tob && a
. Most commutative operators are also associative, but I found this: unspecified.wordpress.com/2008/12/28/… – Entourage&&
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.. – AntimasqueFalse
and first value isTrue
then left association will be efficient as compared to right association. – JoungFalse
you'll still need to evaluate the first operand, which in turn would need to evaluate its first operand and so on. – JainismTrue && True && True && False
be more efficient if left associative? – RocherTrue && 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 toFalse
in 3 reductions as follows:((True && True) && True) && False
would reduce to(True && True) && False
which would reduce toTrue && False
which would reduce toFalse
. This behavior is demonstrated in the following fiddle: jsfiddle.net/Yp5GN Note that every term in the expression is evaluated. – Jainism&&
,||
. Thus it really doesn't matter. – Corniaif (A || B)
is going to be implementedrun 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. – Corniafunction sideEffect() {console.log('run');return true}; false && true && sideEffect() === false /* with nothing printed*/
– Riccardofalse && true && sideEffect()
is equivalent to(false && true) && sideEffect()
. This would reduce tofalse && sideEffect()
and then reduce tofalse
. Hence we have two reductions. However if the logical operators were right associative it would only take a single reduction. The expressionfalse && (true && sideEffect())
would reduce tofalse
in a single reduction. Hence they should be right associative. – Jainism(function(){ console.log(1); })() && (function(){ console.log(2) })() && (function(){ console.log(3) })()
– Lam