3 ms·
i'm talking about the type system itself: keeping the type system implementation itself simple rather than just offering an unsafe work-around this means foreg
by recursivedoubts 3y ago
i'm talking about the type system itself: keeping the type system implementation itself simple rather than just offering an unsafe work-around
this means foregoing system-enforceable type safety and features in some cases as the language level, in order to keep the type system fast and understandable to normies.
- ses1984 3y agoThe 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.
- ragnese 3y agoAgreed. 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.
- mrkeen 3y agoAgreed. Leave out the inheritance, covariance, contravariance, and nullable types. Leave the type system simple enough that you can take a rest and let the computer do the typing work for you.
- bedobi 3y agohard agree on nullable types just make everything non null and use option/maybe to indicate absence wayyyy simpler
- consilient 3y ago> Leave out ... covariance, contravariance This means giving up on first-class functions.
- akkad33 3y agoCan you please explain why
- consilient 3y agoFunction types are contravariant in their inputs. It's the prototypical case, really.
- c_crank 3y agoNot that a big a deal, honestly.
- throwaway17_17 3y agoIsn't this only true in a type system with subtyping? If it does apply to a type system without subtyping I would genuinely like to know if you have any references for it. I was under the impression that variance, as a concept, was immaterial for anything else. Additionally, why would having only invariant functions mean leaving 'first-class' functions? Even assuming subtyping relations between types (simple or complex), invariant functions would still be 'first-class', just not acknowledging of the subtyping relation.