Diaries of a non-consensual JavaScript developer, episode 1: dumdum JS object pointer logic

As part of an elaborate S&M fantasy roleplay, I have over the last handful of months been forced to spend significant time developing a real commercial project in JavaScript and TypeScript.

During this period, I have learned quite a lot about both languages and how they work. And I’m going to share what I have learned with you, partially as a coping mechanism, and also because this will probably be a useful reference.

If the premise that JavaScript is insane and retarded is entirely unfamiliar to you, I suggest you watch the Wat talk (4 minutes) and then come back and read this.

TypeScript does an excellent job alleviating the most common pain points of JavaScript, especially the type-coercion rules, which is the brand of insanity highlighted in the Wat talk.

If you are familiar with Erlang, TypeScript is almost exactly dialyzer.js. It catches the same category of errors and admits roughly the same level of expressiveness. Both programs have the property of only ever performing static code analysis and never changing the runtime behavior of your code.

TypeScript is exceptionally well-done, and I do recommend you use it. It is the best available solution to a problem that should not exist. Said problem is that JavaScript exists and is the only way to run scripts in a web browser.1

With almost no exception, every behavior of JavaScript is ridiculous and insane. You can safely assume that any time there was a design choice between behavior A and behavior B, where behavior A was clearly correct, JavaScript chose behavior B. The language was designed by Satan himself 11 shots deep into a drinking game with Bjarne Stroustrup and Larry Wall.

Every language has warts and pitfalls. In most languages, you can dance around them by writing clean, simple, well-commented code. This is not the case in JavaScript. It is the typical case in JavaScript that the hacky stupid way to do something is the only way to do it.

Further, most JavaScript developers are retarded, and often JavaScript is the only language they know. What to you and me are niggertarded hacks that would leave even Uncle Terry absolutely dumbfounded and utterly lost for words have over time become idioms in JavaScript.

There is a more insidious aspect to this situation. The first time you have to use one of these retarded hacks, you will leave a long comment in your code explaining the hack and why you have to use it. The problem is that said hack will usually be the only way to accomplish a fairly mundane and common task. And after the 50th time you’ve used this hack in the last 8 days, you no longer have the patience to write a comment explaining it. Then, of course, 3 months later, when you come back to re-read your code, these dumb hacks are littered everywhere throughout your code, and only in one instance is there a long comment explaining the hack.

And of course, if you read someone else’s JS code (especially code of a JS dindu), there is zero chance of there being comment explaining the insane hack being used.

Part of the purpose of this series is to be a field guide that documents and explains at least the most common of these hacks all in one place.

Let me give you an example. The source for this example is this commit message2 from a project called Sidekick. Sidekick is a JavaScript library that talks to a browser extension called Superhero from the perspective of a webpage.

The main data structure in sidekick is a Skylight. The name is used used in analogy to this type of skylight

Anyway

Working out dumdum js pointer/messaging logic

the skylight is the thing that sidekick uses to talk to superhero

so the skylight needs to black-box away some internal state. a process is the ideal abstraction to deal with this, but we live in hell so we get to use objects instead.

the first thing the skylight needs to do is let superhero announce itself and record that it has talked to superhero

JS doesn’t have “block on recieve” or anything like that so I thought that I would basically do this

class Skylight
{
    listen()
    {
        window.addListener('message', this.listener);
    }

    ignore()
    {
        window.removeListener('message', this.listener);
    }

    listener(msg)
    {
        this.do_something_with(msg);
    }
}

Of course that doesn’t work because we live in hell. basically here was the problem: listener() gets executed IN THE CONTEXT OF THE WINDOW despite this being an instance method

so I was getting an error message like “this.do_something_with is not callable”. Note that I was NOT getting the error message “this does not have property do_something_with” (which is what the error would be in say Python). Noooooo

instead I was confused for a solid few minutes because I kept saying “yes this does, what are you talking about” and thinking I had made a typo somewhere.

alas, no, my error was not a typo. my error was assuming that javascript behaved in a way that makes sense. which is usually the problem.

note that we CAN’T just do this

listener(skylight, msg)
{
    skylight.do_something_with(msg);
}

class Skylight
{
    listen()
    {
        window.addListener('message', (msg) => listener(this, msg));
    }

    ignore()
    {
        window.removeListener('message', (msg) => listener(this, msg));
    }

}

for two reasons:

  1. if you add an inline lambda as a listener, you can’t remove it, so ignore() wouldn’t work. because the javascript gods record the POINTER to the function, and so the two inline lambdas, despite being identical code, point to different memory locations.Quoth https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

    Note: If a particular anonymous function is in the list of event listeners registered for a certain target, and then later in the code, an identical anonymous function is given in an addEventListener call, the second function will also be added to the list of event listeners for that target.

    Indeed, anonymous functions are not identical even if defined using the same unchanging source-code called repeatedly, even if in a loop.

    Repeatedly defining the same unnamed function in such cases can be problematic. (See Memory issues, below.)

  2. there’s a second layer of jogritude to this, which is that, again, the function is executed IN THE CONTEXT OF THE WINDOW. This isn’t really jogritude. On the contrary, this is a rare example of javascript behaving in a way that makes at least kind of a little bit of sense.this in JS is a bit like self/0 in Erlang.

So in order to work around all of this, I came up with the following basic logic:

listener(skylight, msg)
{
    skylight.do_something_with(msg);
}

class Skylight
{
    constructor()
    {
        // grab pointer to this using hack
        const pointer_to_this = this;
        this.ls =
            function(evt)
            {
                listener(pointer_to_this, evt);
            }
    }

    listen()
    {
        window.addListener('message', this.ls);
    }

    ignore()
    {
        window.removeListener('message', this.ls);
    }

}

and this seems to work

still dumb but whatever

Everything that isn’t Erlang is wrong

Bye

The Founder loves you

By now there are at least a dozen instances in that codebase of me using the

const pointer_to_this = this;
some_function(blah, pointer_to_this);

hack, and of course it is only explained in one place.

I’m going to cut the post off right here and continue from here next time.

Next time I want to dive maybe one level deeper into why this type of bullshit is occurring. As a rule of thumb, if you want to understand thing X, it’s useful to understand X in the context of X’s rewrite layer, and in the context exactly one layer lower in the rewrite stack.

The most common sources of insane behavior are the type-coercion rules, the scoping rules, and the evaluation rules. TypeScript does a superb job mitigating the type-coercion clown show3, and often even catches scope/evaluation bullshit.

This particular retarded bullshit is in the category of scope and evaluation rules. And the basic problem is that JavaScript “functions” really should be called “macros”. The difference essentially boils down to the question “when is the body of the function evaluated”.

I get the sense I’m biting off more than I can chew here. Even if that’s true, whatever I do end up chewing will probably end up being valuable.

The reason I get that sense is that what I planned to write in the remainder of this post turned out to have a rhetorical fork bomb in it.

The menu of topics to discuss is roughly:

  • quotation is too powerful a tool for ape-tier programming languages
  • global scope bad
  • types are fake
  • time is fake
  • assignment bad
  • async/await bad
  • oop bad
  • http bad
  • web bad
  • html bad
  • browsers bad
  • extensions bad
  • typescript good
  • node.js bad
  • concurrency ≠ parallelism
  • concurrency is a must-have
  • parallelism is a nice-to-have
  • concurrency is the correct version of the oop intuition

Joe Armstrong and Alan Kay both wrote extensively about the last batch of points.4 Chapter 3 of SICP really thoroughly dissects all the OOP-bad/time-is-fake/concurrency-good ideas.

I’m at this time learning about Lisp and in general about PL Theory. I’ve picked up a bit of it tangentially from learning various programming languages over the years, but have never seriously studied PL Theory proper. (Not even sure if that’s the thing I want to study). I would greatly appreciate if someone knowledgeable (hi Hans) would spam me with a long orientation and a list of shit I should read/watch/do.

It seems like there’s a lot of lessons to be learned about the correct way to design programming languages, simply by examining in depth the design of a programming language that is definitely designed incorrectly. Antimodels > models.

I’d like to dive into more depth on this with a lot of examples and really nail down the case, if only for my own edification. Perhaps

  • implement OOP in a language that doesn’t have it5
  • tease out the implications of why OOP is bad
  • try to come up with a better way to model the idea of a Turing-complete document (putting aside that this is a bad premise to begin with), and a better language6 that more closely matches the actual problem being solved

See you next time.

The Founder loves you.


  1. Rust faggots who leave comments about web-assembly will be blocked.↩︎
  2. Currently copied into the top of this file, if you want to see the later stage of the evolved context.Commit messages and comments from that repository will be my primary bank of examples in this series.↩︎
  3. If you want a tour of the type-coercion clown show, check out the aforementioned Wat talk (4 minutes)↩︎
  4. For instance: https://archive.ph/i8O4p↩︎
  5. zx did this but stopped short of saying OOP bad (which it is): https://zxq9.com/archives/1838↩︎
  6. One that does not compile to JavaScript. I think TypeScript is pretty much the best we can hope for in the category of “languages that compile to JavaScript”.↩︎

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.