It is obviously very powerful and can model very complex type constraints.
But then you have stuff like this where it is not checking types as I would expect:
interface Foo { bar: string; }
const f = {bar: "foobar"} as Readonly<Foo>;
function someFunc(): Foo {
return f; // No error or warning, even with all strict flags enabled
} class Animal {}
class Dog extends Animal {
woof() {}
}
class Cat extends Animal {
meow() {}
}
let append_animals = (animals: Animal[], animal: Animal) => animals.push(animal)
let dogs = [new Dog()]
append_animals(dogs, new Cat())
dogs.map(dog => dog.woof())
Which if you evaluate, you'll obviously get: Uncaught TypeError: dog.woof is not a function
Whereas Mypy won't typecheck the equivalent Python code: class Animal:
pass
class Dog(Animal):
pass
def append_animals(animals: List[Animal], animal: Animal) -> None:
animals.append(animal)
dogs = [Dog()]
append_animals(dogs, Dog())
It'll throw with: $ mypy types.py
types.py:16: error: Argument 1 to "append_animals" has incompatible type "List[Dog]"; expected "List[Animal]"
types.py:16: note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance
types.py:16: note: Consider using "Sequence" instead, which is covariantTo expand: TS treats `Dog[]` as a subtype of `Animal[]` because `Dog` is a subtype of `Animal`... that work if you only read values from the array... but trying to change the array, you run into trouble. Some languages let you declare covariance (reading ) and contravariance explicitly to address this issue. To my limited knowledge of TS, that's not possible in TS (as it tries to keep things simple and compatible with JS, probably).
The answers in this[1] SO question explain these concepts better than I could.
[1] https://stackoverflow.com/questions/27414991/contravariance-...
Typescript does give you a solution to this problem, namely that you use generics to constrain the parameters of your method:
let append_animals = <T extends Animal>(animals: T[], animal: T) => animals.push(animal)
Your example now gives the expected error.TypeScript does indeed have its quirks, but most of them do not really matter for real-life purposes or can easily be worked around like in the example above.
But like you, I've come across many instances where I got "hey TS aren't you supposed throw an error here?"
This feels like a "haha, gotcha!" moment. Like Gary Bernhardt's Wat talk, it's one single example that looks extremely silly... but has next to no actual impact on anyone using the language regularly, is like the faintest little quirk.
Typescript seems reasonably acceptable. I struggle to think of what I would ask for, what would be significantly massively different in my life if there were a hypothetical much better alternative to Typescript.
I should note that I haven't yet had the pleasure of using a language that handles const-ness properly, as Readonly<T> should be neither a subtype nor a supertype of T
You have to define every possible count of generic arguments if you want to preserve their types. And if you go above that count your type system degrades. I think there’s also a maximum of 7 or so before it doesn’t work. Beyond that and the generic type widens.
For example, Lodash enumerating types for 2 to 7 generic items per function: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/0452...
Admittedly I don’t understand the problem space well. I’ve just seen it happen to me and in others’ code. It might not actually be an issue, or is already fixed.
Entirely possible that PEBKAC, but I’ve found TypeScript’s documentation to be on the worse end of the programming language documentation quality spectrum.
Random but this is what I’m finding GPT-4 best at: translating random questions into domain terminology + providing examples.
interface add extends F { out: this['args'] extends [infer a, infer b] ?
a extends Zero ? b :
a extends Suc<infer n> ? Suc<apply<add, [n, b]>> :
never : never
}The post is experimenting with the type system—which is neat—but before you think you should push this in production (having been in positions to work with heavy-FP’d code), consider an actual FP language if that’s the style you want. The ergonomics are so bad compared to any FP lang→JS option. Currying/partial application, first-class composition/bind/apply, pattern matching not using a ._tag property with a String key, and more are just missing (see: migrating from PureScript/Haskell[0] for fp-ts to see how verbose basic concepts becomes). The other issue is that with TypeScript being multiparadigm and idiomatic TS leaning to imperative/OO due to ergonomics and Java’s legacy on culture/education, there’s a good chance your coworkers/contributors will be expecting a non-FP style which will cause even more friction as you try to justify ‘purity’ to folks that just see a verbose codebase that likely is leaning hard into a lib, quality as it is, like fp-ts which cosplays as Haskell, PureScript, OCaml, Idris, et. al.