89 comments

[ 3.3 ms ] story [ 188 ms ] thread
So, use triple-equals when you don't want type coercion?
Yes, highly recommended. Just look at that chart, insanity!
Yes, or you can use CoffeeScripts which automatically translates == into ===
My favorite bit is where all of these are true:

    '1' == 1 
    '1' == [1]
    '1' == true  
    1 == [1]  
    1 == true  
    [1] == true
But this is false:

    [1] == [1]
Why would it be true? Two different instances of an object don't equal in JS. It compares identity, not value, when dealing with non-value types.
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.
Because equality should be transitive. If a == b and b == c, then it should be the case that a == c.

[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.

> Why would it be true?

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:

       In [1]: [1]==[1]
       Out[1]: True
Ok, let's do a crazy language from Sweden also with dynamic types but which are sane and consistent, like Erlang:

       1> [1] == [1].
       true
Surely it will be broken in Ruby:

         irb(main):001:0> [1] == [1]
         => true
Nah clearly it should be false, these crazy languages just haven't heard about objects and identities and such.
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
And if this isn't clear yet:

    a = {}
    b = {}
    a == a // true
    a == b // false
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.
A better rule is to use triple-equals always, and manually coerce if you really mean it.
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:

  document.body.getAttribute("test"); // null
  document.body.test; // undefined
Except if it's a special property, then getAttribute might still be null, but the property empty string:

  var input = document.createElement("input");
  input.getAttribute("value"); // null
  input.value; // ""
Remember, "" !== null and null !== undefined.
That only solves part of the problem. Coercion also happens for other operators, which ought to have at least had === equivalents. I.e.

   5 + (10 * [2]) + (9 + [1/'']) // 259Infinity
   5 ~+ (10 ~* [2]) ~+ (9 ~+ [1 ~/ '']) // throw exception
Chrome: "This page is in Haitian. Would you like to translate it?"

It might as well be.

You can submit the error, with the option of declaring what language the page is really in.

Unfortunately, "JavaScript" isn't in the list.

Ugh. Many parts of javascript are nice but things like this (''==0?) make me wish for a browser-supported language that was clean.
Yea, or you could use "===".
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.
Yep. Along with `== null`. I also only use "==" with `typeof` since `typeof` always returns a string.
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.

like what? php? visual basic? c? java?

define "clean" and name one language in the history of computing that actually fits that definition.

Lua.
As much as I like lua, it doesn't really meet the challenge I set forth. For one, you forgot to define "clean".

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?

Python, Ruby, Dart just to name a few from the top of my head.

EDIT: removed Java, Rust and Go

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.
Ok good point, 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.

Dart's doing great, you should use it.
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.

The undefined.jpg thing won't happen for variables in strict mode, it'll error.

[].length is what you want for the second. IIRC [] isn't false in Python either.

(comment deleted)
[] is false in Python; so are other empty aggregates like () and {}; collection types implemented in Python typically follow the same pattern. Thus:

  >>> if (): print "tuple"
  >>> if []: print "list"
  >>> if {}: print "dict"
  >>> if set(): print "set"
  >>> if "": print "string"
  >>> import collections
  >>> if collections.Counter(): print "counter"
  >>> if collections.deque(): print "deque"
  >>> 
(Observe that none of these prints anything.)
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).

The real mistake was making [] equal zero.

[] doesn't equal zero, but '' does, and [] is coerced to '' which is coerced to 0, IIRC.
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.
It's almost depressing. It must have taken significantly more work to embed these ludicrous rules when designing the language!
It didn't. The coercing rules are fairly predictable.
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).

> and in the case of string concatenation, can be faster

myth: http://jsperf.com/array-join-vs-string-connect/37

In Firefox 25 on Linux, Array Join Nocopy seems to perform the fastest.
Exactly. I rarely build up an array within a loop and then join it. That kind of thing just seems dirty - use a buffer!

It's usually, e.g.,

['some string ', someVar, ' some rest of string'].join('');

In some cases, nocopy join is faster.

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.

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.

Most programmers prefer code to be more readable for computers than for humans.

They do? How can I stop them all?

you're close, but you're not quiet there.

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.

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.
> Is there really such a need?

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:

        $ node
        > [1,10,9,-3,-4].sort()
        [ -3, -4, 1, 10, 9 ]

Nope, they didn't.
...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".

(comment deleted)
CoffeeScript does that.
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.
Everybody who writes JS should use something like jslint or jshint. JS has ugly parts, but most can be avoided easily.
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.

> It's a dynamically typed language [...]

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?

You're not supposed to use ==. It's there only for backwards compatibility.
Replace == by some other comparison operator like < or > if you prefer. The weird coercion rules still apply.

  >>> '5' < [6]
  true
Why are you comparing a string to an array with a less than operator? when would that ever happen legitimately?

Why would you expect any language to do something that made sense doing that?

> when would that ever happen legitimately?

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?

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.
Reminds me of my favourite Javascript trivia question: What two values for a & b meet the following?

> a === b

true

> Number(a) === a

true

> Number(b) === b

true

> a + b === a

true

> (1 / a) === (1 / b)

false

Edit: SPOILER ALERT

0 and -0.

I never knew about this until I read a shim someone wrote for Object.is().

Wait, am I reading this right?

[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.

NaN == NaN should return false.
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".
Eh. 'number' means 'IEEE 754 floating point value' and NaN is certainly one of those. Abbreviations are tricky.
Yeah, I understand why. I was just pointing out that on the surface, it seems kind of weird.
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.

As others have pointed out, NaN == NaN being false is not surprising (at least according to IEEE floating point semantics ;).

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.

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.

in a NaN === NaN world, (1/0)*0 === 0/0 is true.

There's a reason why Perl separates numeric and string operators, and it's not just to make it confusing for people.
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.)

what does mean 0[object Object]?
You know you're on HN when people are defending this broken aspect of JavaScript, yet hate on the same broken aspect of PHP.
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]

[0] http://tldp.org/LDP/abs/html/exit-status.html