Stack Overflow answer explaining JS in the "Wat" talk
stackoverflow.com
stackoverflow.com
So when you type out({} + []) you are forcing the {} to be something which it is not when you type {} + []. This is part of the 'wat'-ness.
The basic idea is half-hearted: JavaScript wants to allow both of these forms:
if (u)
v;
if (x) {
y;
z;
}
And to do so, two interpretations were made of the opening brace: 1. it is not required and 2. it can appear anywhere.This was a wrong move. Real code doesn't have an opening brace appearing in the middle of nowhere, and real code also tends to be more fragile when it uses the first form rather than the second. (About once every other month at my last job, I'd get called to a coworker's desk when their modifications to my code weren't working, and the problem was that they'd added a line to the "if" without adding curly braces. I eventually just adopted the habit that the curly braces are always required, even when you're only writing one line.)
Fortunately in many cases eval() will replicate the full wat-ness of JavaScript. The JSFiddle code should read:
function out(code) {
function format(x) {
return typeof x === "string" ?
JSON.stringify(x) : x;
}
document.writeln('>>> ' + code);
document.writeln(format(eval(code)));
}
document.writeln("<pre>");
out('[] + []');
out('[] + {}');
out('{} + []');
out('{} + {}');
out('Array(16).join("wat" + 1)');
out('Array(16).join("wat - 1")');
out('Array(16).join("wat" - 1) + " Batman!"');
document.writeln("</pre>");
[Also that is the first time I have written document.writeln in many many many years, and I feel a little dirty writing anything involving both document.writeln() and eval().] >>> "wat" + 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects
>>> "wat" - 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'int'Another, more general, issue is type coercion. You can either have a "strongly typed" or "weakly typed" paradigm (although both terms are somewhat nebulous). I rather like JavaScript's coercing everything--after all, a 1 is a 1 even if it happens to be a string. Thus, I think concatenating a string with a number or trying to do arithmetic with strings makes perfect sense.
A different option--this is what Python does--is to not coerce types. So trying to concatenate a string with a number would give you a type error. However, this also has an advantage: you can now reasonably overload your operators. You can have + add numbers, concatenate strings, append lists...etc.
The real problem comes from trying to have both type coercion and an overloaded + operator; either option by itself is reasonable. JavaScript--probably in an unfortunate attempt to ape Java--has an overloaded + operator that does both concatenation and addition. If it instead had just a separate concatenation operator, the endless problems with + would not arise.
I personally think that if your language is dynamically typed, it should be weakly typed; if your language is statically typed, it should also be strongly typed. So I would (and do) prefer JavaScript/Haskell to Python.
I figure if you're going to be dynamically typed, you may as well be as flexible as possible; it also makes sense from a semantic point of view (my whole a 1 is a 1 is a "1" spiel). On the other hand, if you're enforcing types at compile time, having type coercion just seems weird. (Of course, you can have ad hoc polymorphism like Haskell's type classes that could do something similar, but that's different.)
I think the practical difference between "weak" and "strong" typing is much smaller than the difference between dynamic and static typing.
Finally, I do think the behavior of + is a defect, but not because of coercion. Rather, I don't like how it does two different things. If it only added numbers, it would work perfectly; the other arithmetic operators all work as expected. (Yes, I think the NaN behavior makes sense.)
Is it? In JS: "1" + 1 == "11"; 1 + 1 == 2
If you use any other operator (any reasonable operator), it works: "1" - 1 === 0, "1" - "1" === 0 and 1 - 1 === 0.
var a = undefined = 1
//vs
b = undefined = 1
what happens there? var x = new CustomObject();
x.property.class = "some new class";
Why? Because 'class', even though it doesn't do anything in vanilla JavaScript, is a reserved word that cannot appear in this location. When you want to modify CSS classes, for example, you must use element.className to get that sort of access.I mention this because in reverse, it is the same problem as your example. There are a bunch of default names in JavaScript which you'd expect to have but whose names aren't reserved words and can therefore be used just like variables.
One of these is 'undefined', another is 'Array'. These are both not so bad: if some fool misdefines 'undefined' globally you can just preface your own code with:
undefined = void(0);
...to reset it. Here, 'void' is a Java keyword and so it will never be overwritten. Or if they redefine Array, you have the [] syntax to construct a new array and append elements to it; nobody really writes new Array() for anything anyways.But suppose someone globally redefines 'isNaN' or 'isFinite' or both 'eval' and 'Function'. You're going to have a tremendously difficult time reimplementing those if you don't have them.
The only reason a and b get 1 assigned to them is that the value of an assignment expression is always the RHS, no matter what actually happens to the assignment. Since "undefined" on the global is readonly, the assignment itself is just silently ignored.
All this without strict mode, of course; in strict mode the assignment to undefined throws.
[] + [] to behave like [].concat([])
{} + {} to behave like jquery's $.extend({}, {})
anything else should just barf. IMO.
If all it did was addition, you could simply coerce both sides to numbers and return NaN if the conversion did not make sense. Of course, this would mean needing an extra operator for concatenation, but I think that would be fine.
On the other hand, your approach would make sense if JavaScript did not try to coerce everything. In that case, having + act differently for different types would be a good option, and your ideas would fit right in.
In short: the problem is not type coercion and not overloaded operators; the problem is combining the two in one language.
Anyhow, there's no way to change it without breaking backwards compatability :( And just adding a concatenation operator alone would not do because + would still behave in an annoying way unless it was changed to only do math.
Some operators work on numbers (eg. * , /, ++, prefix +), some work on booleans (eg &&, ?:). Infix + is special cased because it works on either strings or numbers.
JS uses implicit coercion with operators. An operator will newer throw a type error. Instead the operands are converted into the expected type, e.g `a * b` is equivalent to `Number(a) * Number(b)`. The rules for how objects are converted into scalar values are non-obvious, but then again, you shouldn't use numeric operators on objects in the first place.
Yes, there are always good reasons for why a piece of JS code behaves in the strange way it behaves, but I wonder how many hours have been wasted around the world just because of some of these quirks. All the technologies out there have their quirks, but the JS case is pathological.