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.
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.
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...Leave the type system simple enough that you can take a rest and let the computer do the typing work for you.
just make everything non null and use option/maybe to indicate absence
wayyyy simpler
This means giving up on first-class functions.
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.
So... first-class functions with no covariance, contravariance?