Hacker News new | ask | show | jobs
by mccorrinall 1401 days ago
I recently typed my state for zustand (react library for managing state) and it was a horrible experience.

An example from the docs with middleware:

https://docs.pmnd.rs/zustand/typescript#middleware-that-chan...

If anyone calls this “easy, just learn generics” I won’t believe them further.

4 comments

Honestly, if that's what it takes to make something work with Typescript I'm surprised anyone uses it. I thought Scala/Cats was the only language/cult that indulged typism to this extent. **d save us from the static typing Spanish Inquisition and bring back dynamic languages.
Seeing one the worst implementation of static typing and then blaming static typing as a whole is unfair. gradual typing is just an abomination in practice as can be seen in both TS and Python. Maybe a language that's build from the start with gradual typing in mind can make it good but I doubt it. Static and dynamic typing are just at odds in general.
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.

Yeah, this is the sentiment I keep feeling looking at most of the examples being provided in this thread and the article. A lot of the things people are doing here aren't even possible in most static typed object oriented languages or are hard to do and generally discouraged, and violate things like the Liskov Substitution Principle and Open/Closed Principle (especially the Open/Closed Principle; many of these examples are redux adjacent and a lot of them require modifying existing State object instances rather than extending them at a class level per the principle).

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.

We use Rematch/Redux for state management and typing it[1] was pretty easy.

I think it really depends.

[1] https://rematchjs.org/docs/getting-started/typescript

That is terrible. Good luck onboarding new developers or junior team members into your project.

I get playing with technology for side projects, but for something commercial there is no way I would sign off on including that package into a project.

Did you miss this example from the linked post about redux-toolkit? https://github.com/reduxjs/redux-toolkit/blob/4ab8c42cb20ae1...

This is what types look like when you're on the library side. The post made very clear they aren't talking about the app dev side. The library types are complex like this so that the app devs have a smoother experience.

Might help understanding if the author used ‘meaningful’ variable names…
It would be more understandable if you didn’t use single letter type names (someone posted an example from react-query elsewhere)