What I mean by "weird shapes" is, consider `util.promisify`. It converts a function which takes a callback into one which returns a promise. Except that input function which takes a callback can take any number of arguments before said callback, and then the returned function takes the same number of arguments. Furthermore, if the input function contains the field `util.promisify.custom`, `util.promisify` will completely alter its behavior and instead return the value of said field. As a result of all these edge cases, `util.promisify` has a massive, 77+LOC long type signature (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/mast...). And it isn't nearly the worst offender, considering there aren't even any conditional or mapped types here.
Now, I still think TypeScript's complex type system itself is an almost universally good, because the alternative is simply not being able to type-check these interfaces, dealing with `any` types and type errors. I say "almost", because it does encourage API builders to create these super-dynamic weird interface shapes. But, remember, these weird shapes existed before TypeScript, and I'm of the opinion that "useful feature which encourages bad things" is better than "no useful feature but no encouraging bad things" (I believe languages should have more freedom than less, prefer Rust and PureScript over Go and Elm, etc.)
But these weirdly-shaped types are bad. Because, even with TypeScript's awesome type system managing to 100% express them, they still cause problems:
- They make the JIT less effective and interpreter less efficient, because it cannot speculate on a value whose shape is constantly changing, or optimize a function which constantly provides different-shaped inputs and returns different-shaped outputs. - They make it so that TypeScript and JavaScript's interpreter is so complicated that there are bugs. Even with the huge team of people doing amazing work, a language complex as JavaScript and TypeScript is guaranteed to have more bugs. Which leads to... - These weirdly-shaped types rely on bugs and undocumented behaviors in TypeScript's inference and JavaScript's runtime, which make the bugs into features and create weird rules. I already know that TypeScript and JavaScript have plenty of these rules, e.g.
interface Foo {
bar(name: string): Baz
}
is different from interface Foo {
bar: (name: string) -> Baz
}
(the former accepts bivariant function parameters, the latter requires them to be contravariant. And as a result linters will warn against the former syntax even though IMO it looks much nicer...)- And lastly, if you hate JavaScript and TypeScript, good luck creating an alternative. Because now so many JavaScript libraries have these weirdly-shaped interfaces, which exploit bugs and implementation details in the JavaScript runtime and TypeScript type-checker, that any alternative is going to struggle and have minimal impact. TypeScript's weird types are already starting to create some of WATs, like the example above, and creating more features to adapt to JavaScript instead of really thinking hard about them is likely to create more WATs.