Wat [video] (2012)
destroyallsoftware.com
destroyallsoftware.com
I've seen Gary give a few talks. All fantastic.
The Birth & Death of JavaScript is my second favorite. The fact that he stayed in character as someone from 2035 for the entire talk and used a slightly evolved English.. brilliant. I saw that at Strangeloop and have fonder memories than the PyCon rendition on his site.
https://www.destroyallsoftware.com/talks/the-birth-and-death...
Much of it still holds water. I think what's disappointing is how little progress we've come in the 9 years since that video has come out. WASM has been a disappointment -- at least to me.
And the problem WASM solved really would be almost better instead if we just had a ring 4 for web browsers. It seems clear that X86 has won, unless you think that RISC V still has a chance (which is dubious unless you're in the embedded space)
> And the problem WASM solved really would be almost
> better instead if we just had a ring 4 for web
> browsers. It seems clear that X86 has won, unless
> you think that RISC V still has a chance
Mobile has a large and growing lead over desktop[0] for web traffic, and every popular mobile device released for the past ~decade has run on ARM.https://www.theregister.com/2021/11/12/apple_arm_m1_intel_x8...
I'm still waiting for those arm desktops that run Windows...
Therefore there are more browsers on ARM than there on x86.
> I'm still waiting for those arm desktops that run Windows
This seems irrelevant, but MS is shipping them now: https://arstechnica.com/gadgets/2022/11/project-volterra-rev...
M1 Mac Mini running Windows 11 in parallels.
Whether it's NACL/VMWare/WASM/whatever the technology for emulation has been around for decades.
I guess I'm not sure why WASM is so special it needs a decade to come to fruition.
Wat (2012) [video] - https://news.ycombinator.com/item?id=26850630 - April 2021 (28 comments)
WAT – A lightning talk by Gary Bernhardt from CodeMash (2012) - https://news.ycombinator.com/item?id=10096614 - Aug 2015 (1 comment)
Wat by Gary Bernhardt - https://news.ycombinator.com/item?id=10092845 - Aug 2015 (1 comment)
Javascript — Wat - https://news.ycombinator.com/item?id=6307393 - Aug 2013 (3 comments)
WAT - A lightning talk by Gary Bernhardt from CodeMash 2012 - https://news.ycombinator.com/item?id=6054676 - July 2013 (1 comment)
The WHY of WAT - understanding why 'JavaScript is Crazy' - https://news.ycombinator.com/item?id=3517883 - Jan 2012 (52 comments)
Wat - https://news.ycombinator.com/item?id=3515845 - Jan 2012 (98 comments)
(Note: reposts are fine after a year or so - https://news.ycombinator.com/newsfaq.html. Links like the above are just options for the curious.)
None of this matters most of the time, but yeah it's still funny.
In case you want to laugh harder: https://getify.github.io/coercions-grid/
Because yep, it pretty much reduces to the category Type Coercion Rules. But that category in JS is so absurd that normal absurdities feel like minor tomfoolery. (And lest I paint myself as the typical HN anti-JS crusader, I do almost all of my work and personal dev in JS/TS, mostly without complaint.)
Implicit type coercion doesn't matter most of the time? You don't often have bugs because of it?
Whenever I kick off a new npm init, it's always followed by installing typescript, eslint, and usually Zod before I even write the first line of code. If double equals was removed from ECMAScript, I would likely never notice.
It would have made more sense to throw an exception, but JavaScript at it's origin was meant to be a simple scripting language. Nowadays with TypeScript it's much less of an issue.
On the backend all requests and database values are similarly validated and sanitized before any logic occurs.
Anything less is bad code regardless of what languages are used.
Good thing there are never any bugs in validation and sanitization code!
First, for the APIs, you need documentation: https://swagger.io/
From which you can generate JSON schemas and use those to validate in the browser and on the backend. https://www.npmjs.com/package/jsonschema
As well you should be writing a few more schemas for your application state and leverage the regex validation of your input components...
Speaking of which, you also need to sanitize out some potentially nasty input. https://www.npmjs.com/package/dompurify
Obviously this isn't everything and not perfect, but a lot of this tedium can be automated away if you have a few good examples of the happy path and some basic tests in place to prevent quick and dirty changes from poking holes in these layers.
Precedence rules and expectations for 'string append' vs 'add numbers' is different. And then if you through in 'multiplication = repeat.' It's ... now you have this Wat-talk video (which is a classic which I always share with people when the topic of this comes up.)
Maybe I'm just getting stuffy as I get old, but the older I get the more .. explicit.. I want my PL semantics to be.
Yet people love making themselves feel better by latching on to criticism. Signaling your greatness & expertness by beating up on others.
Also if you are facing the world's most popular language & a very successful one people build all sorts of stuff with without complaint, it's a big barrier.
I think most language bigots lack a good narrative. They have a language they love, or a language they hate, but they harp upon specific issues. But to I think most coders, choice of language is actually pretty far down the list, especially if you ask them to ignore the ecosystem of packages/tooling & focus on the language itself. Trying to go beyond scoring points & telling a story about life being actually authentically really different on one path or another keeps being harder and harder to se, as we keep marching through the decades & seeing few really really strong distinct clear advantages anywhere, no one really excelling at their claimed bag, in an unreplicateable way that shows it really is the language, not just ecosystem. (rust being one genuine exception)
Your only defense is to ignore it when you see it and avoid it in your own thinking.
As for what to do, I think socializing a defense has merit. Talking about these dangers, trying to make people smart, critical of criticism, wary of over zeal or conviction. Posting about & sharing & discussing malignent maligning forces, drawing light to radicalization, showing some of the bad impacts this talk has had. Not as a talk itself, but in terms of what a magnet for very much problematic & zealotous negativities.
I see the challenge here as not merely personal, but also societal, in how we deal with especially highly critical highly skeptical behaviors, those that write things off. And the excuses, however shallow, they can stand on to reject.
[Serializable]
public class Foo {
public String bar = "Wat";
}
public class Test: MonoBehaviour {
public Foo foo = null;
void Update() {
Debug.Log(foo.bar); // logs Wat
}
}"Inline serialization can’t represent null, instead, it replaces null with an inline object that has unassigned fields."[0]
The key thing here is the [Serializable] attribute above the class. [Serializable] is an attribute provided by the Unity Engine.
It's used to convert in-memory data structures / objects into a format that can be stored and loaded easily. You could use it to build a save system for your game, but more generally it's used for working on your project in the Unity Editor and then savings those to .scene, .prefab, .asset files and so on. Serializable objects and their fields (public fields are serialized by default) are editable in Unity Editor's Inspector tab, which is great for quickly iterating and working with designers who don't want to open a code editor or Excel in order to modify game object values and scenes.
But Serializable has some "wat" moments and one of them is that it does not support null objects.
MonoBehaviour is a Serializable type provided by UnityEngine. You generally make Game Objects by composing together different MonoBehaviours. Because the "Test" Monobehaviour has a public field"Foo foo", it is serialized by default. But it cannot be null. If it is assigned to null, Unity replaces it with an instance that has default field values.
The correct way to handle this (though not very obvious unless you're deep into Unity) is to use the [SerializableReference] attribute instead. Or even better, follow extrememacaroni's recommendation to use Odin's serializer replacement. It's Apache license and basically a drop-in replacement with some additional features.
No non-standard C# involved here, just the runtime reflection allowed by attributes.
[0] https://docs.unity3d.com/Manual/script-Serialization.html
Sure, you could change the declaration of 'bar' to include 'static', and change the access of 'bar' from 'foo.bar' to 'Foo.bar', but... that's not what the code says.
Doesn't Go do this too?
I asked ChatGPT:
> Gary Berhnhardt gave a famous talk called "Wat" about Javascript. Can you create similar "Wat" style examples for Python?
The examples it gave me were... not that 'wat'-y
Sure, here are some "Wat"-style examples for Python:
1. Unpacking tuples:
>>> a, b = (1, 2)
>>> a
1
>>> b
2
>>> a, b = 1, 2
>>> a
1
>>> b
2
2. Division by zero: >>> 1 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Though this one is interesting:4. Using the is operator:
>>> x = 256
>>> y = 256
>>> x is y
True
>>> x = 257
>>> y = 257
>>> x is y
False"Mutating the immutable" is my personal favourite - the second line of code fails with an exception despite having already succeeded:
>>> t = ([1, 2], 5, 6)
>>> t[0] += [3, 4]
TypeError: 'tuple' object does not support item assignment
>>> print(t)
([1, 2, 3, 4], 5, 6)
I've never been a fan of the decision to have `a += b` sometimes do `a = a + b` and sometimes update `a` in place. My objection was initially just due to theoretical inelegance - but this code shows that decision directly causing a concrete problem.Python does not coerce types, so it doesn't have very many wats like the ones in this talk.
<dis>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
Some more weird/fun stuff about that: If you throw that code into one file instead of a repl, `x is 257` will be True. If you instead instantiate `x = 257` in a different file, and import it, `x is 257` be False again. In a repl if you run them on the same line it ends up as True! >>> x = 257
>>> print(x is 257)
False
>>> y = 257; print(y is 257)
True
If they're in the same file (or same line in a repl), the integer literals are loaded into the `co_consts` object during the initial parse to bytecode, and so they refer to the same object. This only works for integer literals and simple statements that can be optimized to literals by python (e.g. it also works for `x = 257+1; print(x is 258)` >>> dis.dis('y = 257+1; print(y is 258)')
1 0 LOAD_CONST 0 (258)
2 STORE_NAME 0 (y)
4 LOAD_NAME 1 (print)
6 LOAD_NAME 0 (y)
8 LOAD_CONST 0 (258)
10 IS_OP 0
12 CALL_FUNCTION 1
14 POP_TOP
16 LOAD_CONST 1 (None)
18 RETURN_VALUE > True = False
> if not True: print “can’t happen”
can’t happen of = "wat";
for (of of of) of -1/3 == -(1/3)
Where I would ask people what it evaluates to, and give them the choices: "True", "False", or "Python 2 or Python 3?" def add_to_list(value, target_list=[]):
target_list.append(value)
return target_list
list1 = add_to_list(1)
list2 = add_to_list(2)
print(list1) # [1, 2]
print(list2) # [1, 2]
In this example, one might expect list1 to contain only 1 and list2 to contain only 2. However, because the default value for the target_list parameter is mutable, it gets shared between function calls, leading to unexpected behavior.
The other examples it gave me were not that unexpected. Prompt was:> Show me examples of where Python type inference works in unexpected ways.
bit of a weird way to phrase it, I think they're always shared between function calls, you just don't notice when they're not mutable.
[] + []
{} + {}
[] + {}
{} + []
Great examples :)The point is not what the data or naming of the functions/variables is, but what's happening when you run it...
Hint: list.append(value) returns None
But wanting to provide default values of [] is quite common in Python. Seeing this workaround is quite common in Python:
def func(arg=None):
if arg is None:
arg = []
... a = [1,2]
someFunction(a)
b = [3,4]
someOtherFunction(b)
a + b print(list1) # [1, 2]
print(list2) # [1, 2]
Some would expect it to be: print(list1) # [1]
print(list2) # [2]
The reason it's weird is because Python evaluates default arguments at function definition time rather that function call. This means that it's unsafe to use mutable values as default values in params. See the equivalent in JavaScript: function addToList(value, targetList=[]) {
targetList.push(value);
return targetList;
}
const list1 = addToList(1);
const list2 = addToList(2);
console.log(list1); // [1]
console.log(list2); // [2]A pretty funny kind of bug, actually: it was some relatively simple function, passing the unit tests but doing some nonsense on real data. Thing is, it was comparing some sequential ids and working perfectly as long as there were no more than 256 objects.
(Didn’t read the rest, but based on the title it may also be relevant)
> [] + []
''
> [] + {}
'[object Object]'
> {} + []
'[object Object]'
(expected: 0)
> {} + {}
'[object Object][object Object]'
(expected: NaN)
Furthermore, the Ruby one fails for me too, in irb 1.4.2, ruby 3.0.5p211: irb(main):002:0> def method_missing(*args); args.join(" "); end
(irb):2: warning: redefining Object#method_missing may cause infinite loop
/usr/bin/irb: machine stack overflow in critical region (fatal)Your context is implicitly parenthesizing the expressions you are evaluated, treating them as expressions rather than statements. Statements are also expressions of a sort too in JavaScript so uh there's also that. Here's the full wat matrix:
> do {[] + []} while (false);
''
> do {[] + {}} while (false);
'[object Object]'
> do {{} + []} while (false);
0
> do {{} + {}} while (false);
NaN
> do {([] + [])} while (false);
''
> do {([] + {})} while (false);
'[object Object]'
> do {({} + [])} while (false);
'[object Object]'
> do {({} + {})} while (false);
'[object Object][object Object]'
(as a hint, `{} + []` is actually `{}; +[]`, that is not an addition operator in that context).1. [] + {} is actually the string '[object Object]' not an object per say, {} is the object and [] + casts it to string.
2. {} + [] depends on runtimes. In firefox the first {} is actually an empty statement, not an object Object.fromEntries([]) + [] is always the string "[Object object]" just like [] + {}. In node you can do this {} {} + [] or even {1 + 1} + [] for the desired 0. Effectively it is the same as +[]
3. {} + {} is the same. The first {} is an empty statement
Would love any sort of reference to the discussion(s) where the naming and file format name comes up, but didn't find anything relevant last time I looked.
I haven't figured out what is yet. I have a hunch that having a short path to working small and medium complexity projects is a big part of it. But I can't rule out the possibility that the most important thing is just application context... Would anybody still care about JavaScript if the browser hadn't happened?
java was in browsers but couldn't manipulate the dom; same thing with flash
vbscript was in browsers but only msie
technical merit does matter to some extent; forth or assembly would probably not have been adequate, and javascript is far superior to java for this
Arguably, nobody else would ever need a language with JS's original feature set.
(If this was a TED talk, this is where the audience would confusedly applaud)
Just going by syntax (or in this case type coercion) alone is too simplistic for me.
Erlang, Eiffel, C, ASM, JS, Rust, C#, F#, Java, or any other language almost can be good choices depending on circumstances.
As for the specific claim of maturity. Rust is quite solid and attractive to me, but based on the number of changes over the last years not necessarily more mature than any other bigger language out there and arguably quite young albeit with a nice and growing ecosystem. Rust specifically is also in danger of going down the C++ route with a lot of over-engineering though by having too many levels of abstractions (And I write this as someone who learned C++ from a typewriter style written book of Stroustrup himself and loved every minute of it)
It would be ... interesting ... to design a language without WATs. Where the truth tables and the NaNs and whatnot all made a kind of sense. I am left wondering if perhaps a minimum number of WATs would inevitably be present, no matter your design.
https://www.ecma-international.org/publications-and-standard...
https://github.com/php/php-langspec/blob/master/spec/08-conv...
fn get_x() -> i32 {
return return return return return return return return return 1;
}I figure out that "|_| Some(2) =>" in a match arm is just a disguise for the alternation "_ | Some(2)" with prefix separator, but what does the `let` do there?
What you need to know that in js '+' sign will add numbers only if both operands are numbers, otherwise it will coerce both to string type, which is true for {} or []. To convert to string js will use '.toString()' method like this {}.toString() and [].toString(). Now {}.toString() is equal to a string '[object Object]' and [].toString() is equal to an empty string '' (for non empty arrays it will print numbers separated by comma). So if we add anything in JS and on both sides are not numbers we actually add strings! And adding strings means concatenation and it works as you would expect, just put two strings together.
Having that, let's take a look on those examples again
1. [] + {} equal to '[object Object]'
2. {} + [] equal to '[object Object]'
3. {} + {} equal to '[object Object][object Object]'
So no 'magic' involved there. Note that some results are different as in the video, you can check it in console in your browser
UPD: typo
The behavior of your console might be slightly different from the behavior of his console, but there are still many reasonable ways to replicate his results. See my comment below if you're still unsure about this point.
But can you replicate his results for js in any modern browser or node environment? I agree that standards are fixed, but there is definitely a difference between implementation of those standards in different environments
(Just to be clear, I'm not saying any of this to jump on a bashing-JS train. Think of e.g. the abiding love of having been married for 20+ years, it's not like your spouse has no sandpapery parts or faults, we can healthily admit them to each other at this point -- I just have learned not to rub against them.)
Things like
{} + {}
Behave weirdly, only if you don’t ask what happens in every other brace based language when given that syntax: the first {} is parsed as a block statement in C, C++, Java, C#, giving you a +{} expression.Similarly conflating repl output with the value of expressions as though the language is doing something odd.
Or the absurd belief that typeof NaN should be something other than number. While I do love the idea of statically typed languages being forced to crash on NaN results at runtime, that being because the value type is suddenly wrong is stupid.
I for one am tired of people claiming that this is not "weird or bizarre", because not only is it obvious that several parts of JavaScript including the type coercion rules are (at best) strange, it's also absolutely not surprising from its history: The person who originally invented JavaScript wanted to implement Scheme, but management wanted to go for "something similar to Java". JavaScript was prototyped in ten days[1], and I doubt anyone back then really anticipated the role it would later come to have.
There is an entire standards body that aims to hammer JavaScript into something that makes more sense, because, well, we're stuck with. (Or rather, you are stuck with it, I don't do web development.)
https://web.archive.org/web/20200227184037/https://speakingj...
The timing is just so solid.
I also want to say on a personal level that I really like Gary; he has been so friendly to me every time we've met.
And honest to god, how do people program in JavaScript?
This is not to say that there's nothing else weird going on in Javascript, but the things people show off most as "wow, isn't this weird?!" aren't that consequential.
I know people just _want_ to bash JS because it's trendy, but come on.
That’s the problem. This is an unexpected situation and the JS philosophy is to silently do something that tries to make the best of it, rather than e.g. throw an error or warning.
I think it’s perfectly reasonable to bash JS for this. This is a direct consequence of the JS design philosophy. This is similar to the memory-safe vs memory-unsafe debate (except IMO, slightly dumber because I can’t personally think of any reason this is the behaviour you would want, whereas there might be performance reasons for memory-unsafe). It is increasing the bar of producing good code, without any actual benefit.
These issues pretty much all come down to implicit conversions, which you quickly learn to avoid as it’s usually unhelpful (likely a reason for typescript’s popularity).
Although typescript still has the “any” type so maybe it could get a video all on its own.
https://www.typescriptlang.org/play?#code/MYewdgziA2CmB00QHM...
parseInt("10", 0) => 10
parseInt("10", 1) => NaN
parseInt("10", 2) => 2
This doesn't seem particularly surprising to me?[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
parseInt("10", 0, ["10", "10", "10"])
parseInt("10", 1, ["10", "10", "10"])
parseInt("10", 2, ["10", "10", "10"])
https://www.typescriptlang.org/play?#code/ATAOEMCcGcFMEkB2AX...So TypeScript intentionally suppresses this kind of complaint when matching up function types even though it issues them when calling functions directly. The reason for that has a justification, but in contexts like this, I think it is a WAT-worthy interaction.
https://github.com/microsoft/TypeScript/wiki/FAQ#why-are-fun...
I understand why it is this way, but I think it is very surprising and unintuitive behavior.
>> JSON.parse("{}")["__proto__"]["A"] = "T"
"T"
>> W = {}
Object { }
>> W.A
"T"But if you ever actually do this, then... WAT.
What alternative behaviour would you expect?
Deep merging two JSON parsed objects is innocuous enough everywhere else that most don’t think twice about doing it. Lots of widely used libraries that provide deep merging utilities have had security vulnerabilities because of this.
I guess you could argue that the wat is that objects coming out of JSON.parse don’t have null as its prototype.
NaN ** (NaN >> NaN)I only rarely use Twitter though.