21 ms·
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,
by ragnese 3y ago
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.
- CodeSgt 3y agoCould you provide some examples of where it doesn’t (assuming strict = true)?
- ragnese 3y agoI 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 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#noUncheckedIndexedAccess 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...
- CodeSgt 3y agoWow, 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.