|
|
|
|
|
by Aeolun
1400 days ago
|
|
The types in Typescript are amazing. I believe they’re turing complete? The problem is that this allows people to do many really complicated things that’d just be avoided or impossible in other languages. Typescript is a horrible as you make it. |
|
Many of these things are easy to do in JS because JS wasn't built to be static typed object oriented language. Many of these are hard to type because the underlying language is so permissive.
As library authors the desire is to be as permissive as possible, to use the "simplicity" of the untyped JS language to express an API surface that accepts any combination of possible inputs and does the greatest amount of work with that. There are many "JS native" libraries that do that, including JQuery's $ "operator" that was a swiss army knife of a million different tasks all using a single constructor which created object instances that any number of plugins mutated over time. As a JQuery user that was a very easy experience to work with, it's permissivity felt like simplicity and easy-to-learn. As someone who briefly spent time debugging JQuery types definitions in Typescript, that was an incredible nightmare. (That was also many, many versions of Typescript back with fewer typing tools, many of which would have helped a lot, but also made everything even more complex than it was at the time.)
I do feel like a lot of library authors sometimes need to ask themselves as types grow more complicated in their libraries if the trade-offs are worth it. That "simple" API they are trying to give their users, is there a more "complicated" API with simpler types to use instead? Sometimes that's actually the simpler API to use understand too, but many of your end users see your types indirectly and simpler types in the API are simpler experiences of those types.
The article mentions needing to add lots of overloads and that's specifically something I'm thinking about there. Another example in this thread included a function that could take a set of arguments as either an object ({ a?: thing, b?: someOtherThing, c?: thirdThing }) or tuple ([a, b, c]: [thing, someOtherThing?, thirdThing?]) and in both cases many of the parts were optional, complicated further by the thing type being generic itself and the tuple accepting different orders of parameters. Can you just pick one, object or tuple for your API? Users have "less freedom" to do as they wish and the result seems less "simple", but you can eliminate so much complexity in your types. If you do still need both ways, maybe it's reasonable to split from "overloads" to different functions setThingsObject(things: { a?: thing, b?: someOtherThing, c?: thirdThing }) versus setThingsQuickly(things: [thing?, someOtherThing?, thirdThing? ]).
It's easy to armchair quarterback refactor other people's APIs, of course, but it's certainly a factor when I'm building my own APIs: These types are starting to get complicated here, should I refactor to simpler types? Should this API be split into two different/distinct endpoints to simplify the type signature? And so forth.
Some complexity is unavoidable, of course, but sometimes it is worth trading "simple, permissive JS with incredibly complex types" for "tedious explosion of JS that looks complex at first glance with incredibly simple types". It's a deep trade-off space to explore and what's right for any individual project is deeply personal opinion.