Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The type system can be understandable to normies while the implementation is not. I would rather have a type system that is correct and consistent, with the complexity hidden away, than to have to know where the boundary is in the 80/20 system, which seems like the perfect place for bugs to enter your code.


Agreed. Consistent is WAY better for me than inconsistent. One of the reasons I hate TypeScript so much is that it provides type safety except when it doesn't, and it takes a lot of experience to actually track down the MANY (non-obvious) places where it doesn't.


Could you provide some examples of where it doesn’t (assuming strict = true)?


I could provide a novel, but I'll try to keep it to just a few.

Class methods have incorrect variance, even with the 'strictFunctionTypes' setting: https://www.typescriptlang.org/tsconfig#strictFunctionTypes. What's even more insane is that interface/type methods will have different variance depending on the syntax used to write them:

    type SafeFoo = {
        // This will have correct type variance
        foo: (x: string | number) => void
    }
    
    type UnsafeFoo = {
        // This will have incorrect type variance
        foo(x: string | number): void
    }
The `readonly` feature doesn't fully work for object types:

    type Foo = {
        foo: number
    }
    
    type ReadonlyFoo = {
        readonly foo: number
    }
    
    function useFoo(f: ReadonlyFoo) {
        // f.foo = 4 // compile error
        const f1: Foo = f
        f1.foo = 4 // trololol, I just mutated the input even though I promised it was readonly
    }
Classes are kind-of also an interface, which leads to inconsistent/surprising behavior:

    class Foo {}
    
    function useFoo(f: Foo) {
        if (f instanceof Foo) {
            console.log('Cool, a Foo.')
        } else {
            console.log('Uh, wut?')
        }
    }
    
    useFoo(new Foo()) // prints: 'Cool, a Foo.'
    useFoo({}) // prints: 'Uh, wut?'
"TypeScript is structurally typed"... except when it's not. In my above example with the class pseudo-interface, you can "fix" it by including a private field in the class:

    class Foo {
        #nominal: undefined
    }
    
    function useFoo(f: Foo) {
        if (f instanceof Foo) {
            console.log('Cool, a Foo.')
        } else {
            console.log('Uh, wut?')
        }
    }
    
    useFoo(new Foo()) // prints: 'Cool, a Foo.'
    useFoo({}) // This is now a compile error!
Record types are incorrectly typed/handled. A Record<string, string> implies that for ANY string key, there will be a string value. What you almost always actually mean is: Record<string, string | undefined>. Yes, there is a compiler setting (https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAc...) to make all array and record accesses treated as possibly undefined, but that should only be necessary for arrays. The Array type has only one type parameter: the codomain. It has no way of communicating what the domain is (what indexes have values). A Record, on the other hand, DOES allow us to specify both the domain and codomain, so when I write Record<string, string> I'm telling the type system that every string key will be mapped to a string value. Therefore, it should not allow me to assign something that clearly cannot fulfill that contract to it, but it does:

    const x: Record<string, string> = {}
In a type system sense, Record<K, V> is more-or-less equivalent to a function type: (K) => V. Imagine if TypeScript handled actual functions similarly:

    const x: (s: string) => string = (s) => {}

There's more, but like I said, I don't want to spend all day typing...


Wow, thanks for providing those examples! I’ve been using TypeScript for a while and haven’t ever taken care to notice when the type system is failing me.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: