Another random example from Axios: <T = V>(onFulfilled?: (value: V) => T | Promise<T>, onRejected?: (error: any) => any): number;
Or eslint: type Prepend<Tuple extends any[], Addend> = ((_: Addend, ..._1: Tuple) => any) extends (..._: infer Result) => any ? Result : never;
Here's another real example from today... I was trying to figure out how to type "the name of this type's key has to be one of the following strings in this enum, but the type doesn't need to have all the keys". Here's a Stack link with the right answer: https://stackoverflow.com/a/59213781, but it wasn't easy to figure out. At first I thought it would be `[key in Partial<MyEnum>]`, but nope. Maybe optional? `[key in MyEnum]?` kind of works but fails in an new way (see the Stack for details). The correct way to do it is apparently `Partial<Record<MyEnum, unknown>>`, which I NEVER would have been able to figure out. Why the record? Why the unknown? Who knows..?
Don't get me wrong, I love TypeScript for the simpler use cases, and a lot of it IS that, thankfully. But the more complex compositions, especially in popular third-party libs? I've given up lol.
The use of single-letter keywords (K, T, V, P, R, etc.) combined with confusing re-use of punctuation (<> and : and () and []) that mean subtly different things depending on where they're used, on top of how JS already uses them, makes it even more so. Sometimes I wish TypeScript were more verbose and opted for longer, clearer constructs rather than stacked shorthands...
It returns a function. The one that's equivalent to applying the arguments in reverse order. I think that this signature is pretty clear for anyone experienced with a statically typed language with generics and higher order functions.
On the other hand, I have no idea why a compose function that takes exactly 6 arguments, the last of which is function which takes 3 arguments would be a desirable abstraction. But I don't think static typing is necessarily to blame for this -- this just looks like a clunky function that has a clunky type.
I'm fully with you on the second example though.
Sounds like I have stuff to study!
Maybe ramda was an extreme example (with or without typescript, it was so hard to read that our dev team decided to just remove it altogether and replace it with more verbose but easier to read vanillaJS code or equivalent lodash functions). But I come across difficult TypeScript examples nearly every day of my work, where I feel like I'm reading an obfuscated leetcode challenge instead of the straightforward business logic in the rest of the codebase.
Once I finally understand a complex type, my usual reaction is, "That's it? That's all that was trying to express?" It's just an arcane syntax to me. Sounds like learning about generics and higher-order functions in statically typed languages would be a good starting place... thanks!
Libraries exist, in part, to encapsulate high complexity.
There's likely accompanying documentation for the examples you provided.
In some other languages you have similar stuff, with the added complexity of concurrency and memory management related types. If you are struggling with TypeScript, let me tell you about a whole new world of pain called C++.
That's why I think every programmer should learn C++.
Say no to single-letter variables!
It also must have documentation other than the type annotation.
Just look at some of other examples in the sibling comments!