Hacker News new | ask | show | jobs
by amitport 1101 days ago
> "just make it so I don't have to do crazy checks on properties before casting to a class."

You do have 'instanceof' at runtime, so unless you mean some other type of 'class', casting to a class is really a non-issue (and you don't really 'cast'... since dynamic typing and all). If you mean some more advanced concept (e.g., structural type runtime checking), think how you would do it in statically typed languages (is it really better?).

2 comments

Instance of doesn't work sometimes and you have to write explicit property checks. I forget the details because I'm by no means a typescript expert, but I remember being very frustrated by the fact I couldn't just use instance of.
It doesn't work when objects cross global scope boundary (e.g. you get an object from a frame), there's no fixing that, by design the namespaces are different and unrelated.
instanceof only works with JavaScript classes. Not on regular objects that just structurally conform to a type or interface.
Classes aren't the only way to define types?
JavaScript has half decent object construction syntax that avoids the nesting hell of classes:

    function Foo(s) {
        this.s = s
    }

    Foo.prototype.bar = function() {
        console.log(this.s)
    }

    const baz = new Foo("Baz")
    baz.bar()
But it seems Typescript can't handle it?
Well there are 2 issues:

(1) imperative style mutation of the prototype is runtime dependent, and typescript can't really rely on runtime behavior.

(2) Foo has two incompatible types in JS, it is both `(s) => void` and `new (s) => Foo`, typescript by default will assume the first signature (which is usually what people want).

In any case, you can specify the types (though in 99% of the cases, just use a class):

   interface Foo {
       s: string;
       bar(): void;
   }

   const Foo = function(this: Foo, s: string) {
       this.s = s;
   } as {
     new (s: string): Foo;
     (this: Foo, s: string): void;
   };
   
   Foo.prototype.bar = function(this: Foo) {
       console.log(this.s);
   }
   
   const baz = new Foo('Hello');
   baz.bar()
> though in 99% of the cases, just use a class

While that is a fair statement, I've been reading a lot of Typescript code lately and it seems in 99% of cases the selected solution is to use top level functions and globals to avoid the nesting hell class introduces, albeit introducing the global hell in the process. In practice, 'just use a class' doesn't overcome the human element.

Javascript was on the right track originally, but then veered off into the weeds for some reason. I appreciate you pointing out how it can be done. Although the solution is very much second class citizen, sadly. This certainly isn't going to wean developers away from global hell. Hopefully Javascript/Typescript can make some strides in improving this going forward.

I'm not sure what improvments are you suggesting.

We are talking about runtime class creation with dynamic methods, while still adding proper static types. There aren't many language allow this level of flexibility.

You can probably disallow prototype use with existing tools if that's desired, but it is an advanced tool for advanced use cases (which still exist).

We are talking about how poor syntax pushes developers into some strange corners to avoid having to deal with the poor syntax. class ultimately gets you to a decent place, but forces the code into one big giant mess that nobody wants to look at ever again. Prototype function assignment gets you to the same place, with better syntax, but falls short for other reasons.

Given:

    class Foo {
        constructor(x) {
            this.x = x
        }

        bar() {
            return this.x
        }
    }
maybe it becomes:

    object Foo(x) {
        this.x = x
    }

    function Foo.bar() {
        return this.x
    }
Structural typing is a thing.. also there aren't really classes in js in the Java sense, it's all prototype chains.
instanceof only works for inherited types, usually expressed with the class keyword by TypeScript users. It's entirely worthless if you're not using them. Class is the only construct in TS that is both a type and a value.