As a professional mathematician I see in Javscript's sameness operator ==
(also called "abstract comparison", "loose equality") an attempt to build an equivalence relation between entities, which includes being reflexive, symmetric and transitive. Unfortunately, two of these three fundamental properties fail:
A == A
may be false, e.g.
NaN == NaN // false
A == B
and B == C
together do not imply A == C
, e.g.
'1' == 1 // true
1 == '01' // true
'1' == '01' // false
Only symmetric property survives:
A == B
implies B == A
, which violation is probably unthinkable in any case and would lead to serious rebellion ;)
Why equivalence relations matter?
Because that is the most important and prevalent type of relation, supported by numerous examples and applications. The most important application is decomposition of entities into equivalence classes,
which is itself a very convenient and intuitive way of understanding relations. And failure to be equivalence leads to the lack of equivalence classes, which in turn leads to the lack of intuitiveness and unnecessary complexity that is well-known.
Why is it such a terrible idea to write ==
for a non-equivalence relation?
Because it breaks our familiarity and intuition, as literally any interesting relation of similarity, equality, congruence, isomorphism, identity etc is an equivalence.
Type conversion
Instead of relying on an intuitive equivalence, JavaScript introduce type conversion:
The equality operator converts the operands if they are not of the same type, then applies strict comparison.
But how is the type conversion defined? Via a set of complicated rules with numerous exceptions?
Attempt to build equivalence relation
Booleans. Clearly true
and false
are not same and should be in different classes.
Numbers. Luckily, the equality of numbers is already well-defined, in which two different numbers are never in the same equivalence class. In mathematics, that is. In JavaScript the notion of number is somewhat deformed via presence of the more exotic -0
, Infinity
and -Infinity
. Our mathematical intuition dictates that 0
and -0
should be in the same class (in fact -0 === 0
is true
), whereas each of infinities is a separate class.
Numbers and Booleans. Given the number classes, where do we put booleans? false
becomes similar to 0
, whereas true
becomes similar to 1
but no other number:
true == 1 // true
true == 2 // false
Is there any logic here to put true
together with 1
? Admittedly 1
is distinguished, but so is -1
. I personally don't see any reason to convert true
to 1
.
And it gets even worse:
true + 2 // 3
true - 1 // 0
So true
is indeed converted into 1
among all numbers!
Is it logical? Is it intuitive? The answer is left as exercise ;)
But what about this:
1 && true // true
2 && true // true
The only boolean x
with x && true
being true
is x = true
. Which proves that both 1
and 2
(and any other number than 0
) convert to true
! What it shows is that our conversion fails another important property -- being bijection. Meaning that two different entities can convert to the same one. Which, by itself, does not have to be a big problem. The big problem arises when we use this conversion to describe a relation of "sameness" or "loose equality" of whatever we want to call it. But one thing is clear -- it is not going to be an equivalence relation and it is not going to be intuitively described via equivalence classes.
But can we do better?
At least mathematically -- definitely yes!
A simple equivalence relation among booleans and numbers could be constructed with only false
and 0
being in the same class. So false == 0
would be the only non-trivial loose equality.
What about strings?
We can trim strings from whitespaces at the beginning and the end to convert to numbers, also we can ignore zeroes in front:
' 000 ' == 0 // true
' 0010 ' == 10 // true
So we get a simple rule for a string -- trim the whitespaces and zeroes in front. Either we get a number or empty string, in which case we convert to that number or zero. Or we don't get a number, in which case we don't convert and so get no new relation.
This way we could actually get a perfect equivalence relation on the total set of booleans, numbers and strings! Except that ... JavaScript designers obviously have another opinion:
' ' == '' // false
So the two strings that both convert to 0
are suddenly non-similar! Why or why? According to the rule, strings are loosely equal precisely when they are strictly equal! Not only this rule breaks the transitivity as we see, but also it is redundant! What is the point of creating another operator ==
to make it strictly identical with the other one ===
?
Conclusion
The loose equality operator ==
could have been very useful if it were abiding to some fundamental mathematical laws. But as it sadly doesn't, its usefulness suffers.
==
by default,===
stands out and lets me know something important is going on.