I think the point is that it's interesting that a bunch of textually-different things are considered equal, but the one textually identical pair are considered not equal.
Why are you being a dick? He explained the difference accurately - JS compares objects as individual objects. Arrays are objects. The other languages you cited do not work that way.
Here's another example:
4 == 4 // true
a = new Number(4);
b = new Number(4);
a == b // false
a == a // true
a.valueOf() // 4
b.valueOf() // 4
a.valueOf() == b.valueOf() // true
Rather his tone of "Why would it be true?" sounded dick-ish. As in "How could possibly one consider [1] == [1] be true, are you crazy? It should obviously be false".
So I gave a couple of examples from other dynamic languages where the the sane behavior is "obvious".
I am not being a dick I am saying the language is broken. Explaining the historical context or the internal implementation of it doesn't make it unbroken. Like one can explain why COBOL uses this construct or that and how it came to be, doesn't make COBOL better or more appealing. One of course might not have a choice and be stuck using it but lying to oneself about how awesome it is, is not necessary.
> The other languages you cited do not work that way.
The don't, and I like how they work better.
Now whether one has a choice to use or not use JS is a different topic. Usually there is no choice on the client side. But somehow extolling Javascript typing rules as being sane, making sense, or as someone below put it "brilliant" is a bit silly.
No, he didn't ask it like that at all. You're putting words in his mouth instead of reading the sentence in the context of the two sentences that followed it, which made it a completely reasonable question.
This is a dangerous rule; if you put this rule in the hands of a programmer who is not used to Javascript and doesn't really understand the type system, you can just as easily run into problems.
This is because you require the programmer to always understand the output (and defaults) of every call made. Sometimes it's undefined. Sometimes it's null. Sometimes it's empty string. This takes experience.
Example, the default for getAttribute is null, but an undefined property is undefined:
Since comparing strings and numbers generally doesn't make a lot of sense, you could just consider it a case of "undefined behavior". As many other people will write: There's only one case I can think of where the unsafe-equals operator is reasonable and that's `a == null` to check for null/undefined.
Most languages call those situations by the name "runtime error", that is, when it is even possible that they happen at runtime.
Now, I understand that javascript runs in a completely different kind of environment, and it does have the liberty of making things differently, up to a certain point. But making '' == 0 true may be a bit too far.. And making [1] == 1 true is clearly too far.
You forgot to define "clean".
Also, java is in your list. Java had its chance. it failed. did you forget that? So has Dart. how's dart doing?
Rust as a web scripting language doesn't even make sense.
I'll define "clean" as a language that has been intentionally designed with a goal to make language features interact in intuitive ways - i.e., that if something looks like X, then it also acts like X; and also behave safely, i.e., undefined behavior is minimized or eliminated if possible.
'Evolved' languages tend to be less clean than 'designed' ones, and also any language accumulates cruft with time if features are added carelessly - for example, C++ can never be 'clean' even if C++11 tries to be that way, because compatibility.
Python, go, Haskell are much cleaner than JS. I'm not claiming that those languages are perfect, they definitely aren't, but there is a significant difference - just as in the saying that [earth is flat] is wrong, [earth is a sphere] is wrong, but they're nowhere near equally wrong.
As a Pythonista Javascript is really strange. Let's combine two arrays with a + b. Wait, why is it now a string? Why did it just create a undefined.jpg when I run filename + '.jpg'? Ah damn, I named it filepath. I guess I check if this object is empty by comparing it to false, works for arrays right? Except it doesn't and it doesn't work for arrays either because they might contain a 0.
The lesson is, always look for a method that does what you want.
You really wouldn't want an empty object to be equal to zero, since it might have a prototype that gives it functionality (and having the behavior conditional upon that would make it preposterously complicated to use correctly).
What's really interesting thing is that all numeric coercions go through a string coercion, and that coercion doesn't (at least in Chrome and Firefox) have to even return a string. Presumably, the toString() method is run repeatedly until either a string or a number comes out.
So a simple fraction type can be done like this:
function Fraction(n, d) {
this.n = n;
this.d = d;
}
Fraction.prototype.toString = function() {
return this.n / this.d;
};
//Demo
var x = new Fraction(1, 2);
alert(x * 38); //19
alert(x + x); //1
You call it strange, I call it broken. Python has strong types, so does Erlang. Javascript has weak types and I don't like it.
Whether one can avoid it or not is a different argument, as obviously there aren't that many choices on the browser, but I often argue it is a broken behavior, it annoys me every time I have to use Javascript.
I program JS as my day job. It's a broken POS. You can do cool things with it but nothing can disguise the fact that it's only popular due to an accident of history, not because it's an appropriate language for the tasks it's used for.
The coercing rules are fairly predictable, but oftentimes incomprehensible until you understand what's happening behind the scenes.
It's one of the biggest weaknesses of JavaScript. Much bigger than callback hell, garbage collection cycles etc.
My preferred style uses Array.prototype.join('') for string concatenation, non-coercive equality operations, and only using the + operator when doing math. It's a little cumbersome, but avoids ambiguity upon reading (and in the case of string concatenation, can be faster).
But most people do string concatenation in situations where performance of the concatenation really doesn't quite matter so much. I optimize for readability, understandability, and prevention of stupid silly mistakes first. Performance comes later.
I think this is brilliant. Most programmers prefer code to be more readable for computers than for humans. I think the problem is that we grow up with the notion that computer programs have to be very strict, in syntax, types, comparisons etc. Is there really such a need?
It was for a similar reason that many people used to like XHTML over HTML. Not always because they needed to validate it, but to please the computer.
As programmers we all know that Javascript has some crazy edge cases.
I'm not actually all that impressed by this illustration of Javascript's equality rules. This makes it look crazy when in fact it just gives the many edge cases much stronger visual weight.
The truth is, Javascript has lent strongly toward being natural in many of the most common cases -- things like being able to do `args = args || []` or simply prepending `!` to anything. Then, sometimes when you end up comparing NaN to undefined you aren't sure what the result should be or how you ended up there.
Funny thing is that often when I've run into those problems myself, I pull on the thread and find it was really caused by my own poor design somewhere else.
I prefer rigour for my own sanity rather than the computer's sake. In scala if I make a typo somewhere I get a compiler error that tells me the relevant line. In javascript I get "undefined has no property 'blah'", on a completely different line, at runtime.
Because programs, unless they deal with machine learning, statistics or other intentional approximations, deal with _strict_ things.
If you put $0.5 in an account you don't expect to find $0.2 there when you read it back or find "Hi Jim" there. You expect to find $0.5, when you seek to byte offset 19553 in the file you expect to read starting with byte 19553 on next read not "something in the vicinity of byte 19553".
[] + [] should not be '', never, it never makes sense, it is not brilliant it is rather profoundly stupid.
Ok, language is broken, we established it, let's take a look at the library functions. At least there cooler heads prevailed:
...and that's why no one but brand new beginners use "==". If it was up to me, "use strict" would turn "===" into "==" and there would be no "===".
It's too late now because it would break a lot of applications, so a new mode might be required, like "use not-completely-broken-comparisons-by-default".
Yup, but CoffeeScript introduces 99 other ways for beginners to create awful code, it's way easier to shoot yourself in the foot than with just JavaScript. I still prefer CoffeeScript, though.
I honestly have no idea what's so confusing. I've been working with javascript for over a decade and have NONE of this memorized, and I never look it up or check it in the repl. It's simply not an issue if you're using jshint and unit testing your code.
If you come from a static language background and you keep expecting a type-checker will save you from doing silly things like adding arrays to strings, you're always going to hate Javascript. It's a dynamically typed language, and so you have to learn the quality-control tools and practices for dynamically typed languages.
I don't think being a dynamically typed language has anything to do with having weird type coercion rules for the most basic operations. Nor with allowing seemingly invalid operations (e.g. adding arrays and strings) and returning a value instead of raising an error on those cases.
> I honestly have no idea what's so confusing.
Really? Comparing JS's == and + operators to other languages' (dynamically typed or not), wouldn't you say that JS's semantics are more complicated/confusing than what they should be?
Instead of asking when that happens legitimately, why don't you ask why does it evaluate to a legitimate value?
> Why would you expect any language to do something that made sense doing that?
Because raising an error in those cases would:
1. Make the language simpler and easier to understand (and to implement). No coercion rules, not guessing what some other developer meant when they wrote `a == 0` (are they asking for numeric equality, or could `a` be a string or a boolean value too?).
2. Make it easier to identify the bug in case it should happen.
Let me ask you, do you also think syntax errors are unnecessary too? Because, why would anyone write a program with invalid syntax? Why would one expect any language to do something that made sense, like not compiling/running the code, in those cases? Wouldn't it be better if, for example, in case a line contains a syntactic error, the interpreter would just discard that like and run the program anyway?
You can't make history something it's not. You might or might not remember visiting the web soon after javascript was released. It involved a lot of broken code, and a lot of error pop ups. Javascript was, historically, a dumb easy scripting language for simple interactive effects on the web.
When we're talking about a script that is downloaded from a server, and runs in a browser, that is a much different kind of situation than a programming language usually encounters. It is in the browser maker's interest to be liberal about what it accepts. this is the core reason for the success of html, and the failure of xhtml.
as much as the makers of javascript would like to remove certain problematic features, they cannot without breaking old existing content.
Now if you want a strict language in the browser that disallows that sort of nonsense, it's easy. Just run JSLINT on your code before deploying it. JSLint considers as an error, pretty much all of your complaints here. If you don't like some aspect of javascript, you can simply not use it. If there's something jslint doesn't catch, I suppose you could always add stuff to jslint. JSLINT is your static compiler.
There are solutions to your pain and suffering. you don't have to endure it.
> If you come from a static language background and you keep expecting a type-checker will save you from doing silly things like adding arrays to strings, you're always going to hate Javascript.
Hmm funny I come from a dynamic language background and never had a problem with the language telling me adding an empty list to an empty list should somehow be empty string. That is batshit crazy. Those are not silly things those are basic 101 strong type system checks that very dynamics languages like Ruby and Python can do.
"Strong-typing vs weak-typing" is actually irrelevant. It's still an error at run-time, and unless your quality control tools and practices actually run the code (like unit tests do), you're not going to know about them.
As you move beyond native types, duck-typing (like in python) completely subverts the strong type-checks anyway.
Dynamic typing (a type belongs to the value, not the variable or "slot") is not same as weak typing (a type may get silently messed up). Python is a good example of dynamically but yet strongly typed language. There exists a good argument against the whole notion of "strong"/"weak" distinction (mainly because it is not a bipartite property, and well-defined type coercion can be regarded as safe) but the main idea holds.
NaN !== NaN is intended, and is the same in any other language that has NaN. It is never supposed to be equal to anything else, not even itself.
As for the rest of those quirks, they mainly have to do with type coercion, the rules for which are not as difficult to learn as the whole table in the submission. The fact that type coercion is performed for == is why nobody uses ==. You also should never try + or * on arrays (and why would you?). With some simple best practices, you never end up running into most of these quirks in practice.
Another fun thing about NaN is that typeof NaN === 'number'. Which, you know... seems counterintuitive based on the fact that NaN means "Not a number".
Some explanations: The rule of thumb is that, == does type coercion only when at least one of operands is primitive (non-Object), and does only the identity check otherwise. Therefore:
[1] != [1] since both [1] are distinct arrays; [1] == 1 since 1 is primitive so [1] gets converted to primitive, in this case, '1', and '1' == 1. (The conversion to primitive is complicated, mainly because it has to select between ToNumber and ToString depending on the context. See Section 9.1 of ECMA-262 5th edition.) Likewise, [] == 0 since [] gets converted to '' and coerced to 0.
NaN != NaN is a well-known facet of IEEE 754 floating point system, and you'll see similar phenomenon in SQL's NULL handling.
[0] + [0] == '00' since both operands are coerced to string (no numeric operands there, so we assume both operands will eventually become string); [0] * [0] == 0 since both operands are coerced to number (no multiplication on string).
...Yeah, I agree on your thesis that ECMA-262 is seriously f*cked up.
NaN is mostly equivalent to the _mathematical_ concept of undefined. It cannot be equal to anything, even itself, because there are multiple mathematical operations that can result in NaN that are not equivalent in value. Considering their result equivalent would be in error.
A lot of this is sort of strange or unexpected, and that's obviously not a good thing. But 99% of the operations in that table are ones that I would never be doing in the first place.
`[null] + {} == 'null[object Object]'`? Okay, fine. I might not have known that off the top of my head, but I also make it a habit not to add/concat arrays and objects.
Unexpected type coercion is probably responsible for about 1% of the bugs I write. It's just not that big of a deal once you learn the common cases (`'1' == 1`, etc.)
I had to laugh when I saw this. It made me think of the Joshua Bloch interview in Coders at Work:
> Seibel: I was reading Java Puzzlers and Effective Java and it struck me that there are a lot of little weird corners for a language that started out so simple.
> Bloch: There are weird corners, yes, but that's just a fact of life; all languages have them. You haven't seen a book called C Puzzlers. Why not?
> Seibel: Because it's all puzzlers.
> Bloch: Yep. It would take up a shelf. In Java, the puzzlers are worth collecting precisely because you think of it as a simple language. Every language has its corner cases and Java has few enough that they're for the most part fun and interesting.
Interesting, maybe. But I hope people are not using all of these. [0]
89 comments
[ 3.3 ms ] story [ 188 ms ] thread[1] == 1 and 1 == [1], so it should be the case that [1] == [1].
Just because it's what happens when you apply the rules of the language doesn't mean it actually makes sense.
It is funny that you are asking why would [1] == [1] possibly considered to be true.
Let's see in a language with a sane and consistent typing system like Python:
Ok, let's do a crazy language from Sweden also with dynamic types but which are sane and consistent, like Erlang: Surely it will be broken in Ruby: Nah clearly it should be false, these crazy languages just haven't heard about objects and identities and such.Here's another example:
And if this isn't clear yet:So I gave a couple of examples from other dynamic languages where the the sane behavior is "obvious".
I am not being a dick I am saying the language is broken. Explaining the historical context or the internal implementation of it doesn't make it unbroken. Like one can explain why COBOL uses this construct or that and how it came to be, doesn't make COBOL better or more appealing. One of course might not have a choice and be stuck using it but lying to oneself about how awesome it is, is not necessary.
> The other languages you cited do not work that way.
The don't, and I like how they work better.
Now whether one has a choice to use or not use JS is a different topic. Usually there is no choice on the client side. But somehow extolling Javascript typing rules as being sane, making sense, or as someone below put it "brilliant" is a bit silly.
This is because you require the programmer to always understand the output (and defaults) of every call made. Sometimes it's undefined. Sometimes it's null. Sometimes it's empty string. This takes experience.
Example, the default for getAttribute is null, but an undefined property is undefined:
Except if it's a special property, then getAttribute might still be null, but the property empty string: Remember, "" !== null and null !== undefined.It might as well be.
Unfortunately, "JavaScript" isn't in the list.
Now, I understand that javascript runs in a completely different kind of environment, and it does have the liberty of making things differently, up to a certain point. But making '' == 0 true may be a bit too far.. And making [1] == 1 true is clearly too far.
define "clean" and name one language in the history of computing that actually fits that definition.
For another, if you put lua in the browser instead of javascript, how long do you think it will be before lua starts growing horrible warts as well?
EDIT: removed Java, Rust and Go
> So has Dart. how's dart doing?
Very well. It has a sane type system. Its VM is about twice as fast as V8, has a nice IDE that comes with it.
'Evolved' languages tend to be less clean than 'designed' ones, and also any language accumulates cruft with time if features are added carelessly - for example, C++ can never be 'clean' even if C++11 tries to be that way, because compatibility.
Python, go, Haskell are much cleaner than JS. I'm not claiming that those languages are perfect, they definitely aren't, but there is a significant difference - just as in the saying that [earth is flat] is wrong, [earth is a sphere] is wrong, but they're nowhere near equally wrong.
The lesson is, always look for a method that does what you want.
[].length is what you want for the second. IIRC [] isn't false in Python either.
http://ideone.com/ROMcag
The real mistake was making [] equal zero.
So a simple fraction type can be done like this:
Whether one can avoid it or not is a different argument, as obviously there aren't that many choices on the browser, but I often argue it is a broken behavior, it annoys me every time I have to use Javascript.
It's one of the biggest weaknesses of JavaScript. Much bigger than callback hell, garbage collection cycles etc.
My preferred style uses Array.prototype.join('') for string concatenation, non-coercive equality operations, and only using the + operator when doing math. It's a little cumbersome, but avoids ambiguity upon reading (and in the case of string concatenation, can be faster).
myth: http://jsperf.com/array-join-vs-string-connect/37
It's usually, e.g.,
['some string ', someVar, ' some rest of string'].join('');
Thus.
"Can be" faster.
But most people do string concatenation in situations where performance of the concatenation really doesn't quite matter so much. I optimize for readability, understandability, and prevention of stupid silly mistakes first. Performance comes later.
It was for a similar reason that many people used to like XHTML over HTML. Not always because they needed to validate it, but to please the computer.
I'm not actually all that impressed by this illustration of Javascript's equality rules. This makes it look crazy when in fact it just gives the many edge cases much stronger visual weight.
The truth is, Javascript has lent strongly toward being natural in many of the most common cases -- things like being able to do `args = args || []` or simply prepending `!` to anything. Then, sometimes when you end up comparing NaN to undefined you aren't sure what the result should be or how you ended up there.
Funny thing is that often when I've run into those problems myself, I pull on the thread and find it was really caused by my own poor design somewhere else.
They do? How can I stop them all?
Programmers want code to be readable to an IDE.
IDE's can parse, interpret, precompile, and suggest.
Languages that are very wishy washy and dynamic like javascript are absolute hell for an IDE.
Yes.
Because programs, unless they deal with machine learning, statistics or other intentional approximations, deal with _strict_ things.
If you put $0.5 in an account you don't expect to find $0.2 there when you read it back or find "Hi Jim" there. You expect to find $0.5, when you seek to byte offset 19553 in the file you expect to read starting with byte 19553 on next read not "something in the vicinity of byte 19553".
[] + [] should not be '', never, it never makes sense, it is not brilliant it is rather profoundly stupid.
Ok, language is broken, we established it, let's take a look at the library functions. At least there cooler heads prevailed:
Nope, they didn't.It's too late now because it would break a lot of applications, so a new mode might be required, like "use not-completely-broken-comparisons-by-default".
If you come from a static language background and you keep expecting a type-checker will save you from doing silly things like adding arrays to strings, you're always going to hate Javascript. It's a dynamically typed language, and so you have to learn the quality-control tools and practices for dynamically typed languages.
I don't think being a dynamically typed language has anything to do with having weird type coercion rules for the most basic operations. Nor with allowing seemingly invalid operations (e.g. adding arrays and strings) and returning a value instead of raising an error on those cases.
> I honestly have no idea what's so confusing.
Really? Comparing JS's == and + operators to other languages' (dynamically typed or not), wouldn't you say that JS's semantics are more complicated/confusing than what they should be?
Why would you expect any language to do something that made sense doing that?
Never. Aka, on buggy code.
Instead of asking when that happens legitimately, why don't you ask why does it evaluate to a legitimate value?
> Why would you expect any language to do something that made sense doing that?
Because raising an error in those cases would:
1. Make the language simpler and easier to understand (and to implement). No coercion rules, not guessing what some other developer meant when they wrote `a == 0` (are they asking for numeric equality, or could `a` be a string or a boolean value too?).
2. Make it easier to identify the bug in case it should happen.
Let me ask you, do you also think syntax errors are unnecessary too? Because, why would anyone write a program with invalid syntax? Why would one expect any language to do something that made sense, like not compiling/running the code, in those cases? Wouldn't it be better if, for example, in case a line contains a syntactic error, the interpreter would just discard that like and run the program anyway?
When we're talking about a script that is downloaded from a server, and runs in a browser, that is a much different kind of situation than a programming language usually encounters. It is in the browser maker's interest to be liberal about what it accepts. this is the core reason for the success of html, and the failure of xhtml.
as much as the makers of javascript would like to remove certain problematic features, they cannot without breaking old existing content.
Now if you want a strict language in the browser that disallows that sort of nonsense, it's easy. Just run JSLINT on your code before deploying it. JSLint considers as an error, pretty much all of your complaints here. If you don't like some aspect of javascript, you can simply not use it. If there's something jslint doesn't catch, I suppose you could always add stuff to jslint. JSLINT is your static compiler.
There are solutions to your pain and suffering. you don't have to endure it.
Hmm funny I come from a dynamic language background and never had a problem with the language telling me adding an empty list to an empty list should somehow be empty string. That is batshit crazy. Those are not silly things those are basic 101 strong type system checks that very dynamics languages like Ruby and Python can do.
As you move beyond native types, duck-typing (like in python) completely subverts the strong type-checks anyway.
> a === b
true
> Number(a) === a
true
> Number(b) === b
true
> a + b === a
true
> (1 / a) === (1 / b)
false
0 and -0.
I never knew about this until I read a shim someone wrote for Object.is().
[1] == [1] is false, but [1] == 1 and 1 == [1]?
[0] == [0] is false, but [0] == '0' and '0' == [0]?
[] == [] is false but [] == 0 and 0 == []?
NaN === NaN is false?
[0] + [0] = '00', but [0] * [0] = 0?
Who designed this crazy language? It seems to take every quirk of every other language and combine them in the weirdest way possible.
[] == [] is false, so it must be comparing pointers, right? Common gotcha of Java, etc.
...Wait, NaN === NaN is false.
As for the rest of those quirks, they mainly have to do with type coercion, the rules for which are not as difficult to learn as the whole table in the submission. The fact that type coercion is performed for == is why nobody uses ==. You also should never try + or * on arrays (and why would you?). With some simple best practices, you never end up running into most of these quirks in practice.
[1] != [1] since both [1] are distinct arrays; [1] == 1 since 1 is primitive so [1] gets converted to primitive, in this case, '1', and '1' == 1. (The conversion to primitive is complicated, mainly because it has to select between ToNumber and ToString depending on the context. See Section 9.1 of ECMA-262 5th edition.) Likewise, [] == 0 since [] gets converted to '' and coerced to 0.
NaN != NaN is a well-known facet of IEEE 754 floating point system, and you'll see similar phenomenon in SQL's NULL handling.
[0] + [0] == '00' since both operands are coerced to string (no numeric operands there, so we assume both operands will eventually become string); [0] * [0] == 0 since both operands are coerced to number (no multiplication on string).
...Yeah, I agree on your thesis that ECMA-262 is seriously f*cked up.
I kinda makes sense if you consider that both 0/0 and Infinity0 evaluate to NaN. `0/0 == Infinity0` being true would be an even weirder behaviour.
in a NaN === NaN world, (1/0)*0 === 0/0 is true.
`[null] + {} == 'null[object Object]'`? Okay, fine. I might not have known that off the top of my head, but I also make it a habit not to add/concat arrays and objects.
Unexpected type coercion is probably responsible for about 1% of the bugs I write. It's just not that big of a deal once you learn the common cases (`'1' == 1`, etc.)
Weak typing and automatic coercions lead to some confusing results, but JS fares better than PHP.
> Seibel: I was reading Java Puzzlers and Effective Java and it struck me that there are a lot of little weird corners for a language that started out so simple.
> Bloch: There are weird corners, yes, but that's just a fact of life; all languages have them. You haven't seen a book called C Puzzlers. Why not?
> Seibel: Because it's all puzzlers.
> Bloch: Yep. It would take up a shelf. In Java, the puzzlers are worth collecting precisely because you think of it as a simple language. Every language has its corner cases and Java has few enough that they're for the most part fun and interesting.
Interesting, maybe. But I hope people are not using all of these. [0]
[0] http://tldp.org/LDP/abs/html/exit-status.html