3 ms·
I share this point of view. For the last few years, this perfectly reasonable and balanced viewpoint was taboo so it's good to see more and more people approach
by cryptica 6y ago
I share this point of view. For the last few years, this perfectly reasonable and balanced viewpoint was taboo so it's good to see more and more people approaching the debate rationally as opposed to waging a religious war over gut feelings.
Personally, I have no issue with TypeScript purely as a language and I concede that it provides much better IDE code completion than JavaScript. That said, I don't agree that its benefits offset the drawbacks of the transpilation step, the source mapping and the added versioning complexity between JS and TS.
I also miss the way that JavaScript encouraged people to define very simple function signatures (e.g. strings, numbers, plain objects/clones as arguments and return values).
For example, I really liked how the React/Redux community came up with a philosophy around cloning state objects before returning them from functions in order to prevent unexpected mutations later (essentially force everything to be pass-by-value). I think this philosophy does not translate very well to TypeScript which encourages developers to pass around complex live instances (which have their own methods) instead of raw objects and other primitive state representations.
As Alan Kay pointed out, OOP is not primarily about objects, "The big idea is messaging". Instances should communicate with each other via simple insterfaces using simple messages; they should avoid passing complex live instances to each other. If an instance has methods, that is a complex live instance and it shouldn't be used for messaging between different components.
- acemarke 6y agoI maintain Redux, and I can confirm that TS does _not_ "encourage devs to pass around complex live instances" any more than normal JS does. You can do FP and immutable updates in TS, just like you can do any other code. See our "usage with TS" docs pages for examples: - https://redux.js.org/recipes/usage-with-typescript https://redux.js.org/recipes/usage-with-typescript - https://react-redux.js.org/using-react-redux/static-typing https://react-redux.js.org/using-react-redux/static-typing - https://redux-toolkit.js.org/usage/usage-with-typescript https://redux-toolkit.js.org/usage/usage-with-typescript
- cryptica 6y agoWhat you're claiming here does not match the evidence from your links. For example, in your second link, one of the functions expects a 'complex' instance DispatchProps which has a method toggleOn... This abstraction doesn't make sense conceptually. What is a DispatchProps? It doesn't adhere to Alan Kay's notion of a 'message', it's clearly a structure (it's a complex one because it exposes a method). Components should communicate to each other via messages, not structures. Also, the method builder.addMatcher(...) accepts a function as an argument - It doesn't seem like an ideal abstraction either. Functions are not messages. Also, the thunkSendMessage function signature is very complex; the return type is highly convoluted. That's definitely not a message. Overall, I see a lot more of these complex instances being passed around in these examples than used to be the case with JS when mostly just raw objects were being passed as arguments and returned. You can already see the complexity seeping into the interfaces. Just a few years of TypeScript is distorting the original philosophy. I remember Dan Abramov was very careful about what to pass into functions and what to return from them and made it a point to encourage cloning objects using the ... spread operator. That philosophy appears to have been forgotten.
- kilburn 6y ago> For example, in your second link, one of the functions expects a 'complex' instance DispatchProps which has a method toggleOn... This abstraction doesn't make sense conceptually. What is a DispatchProps? It doesn't adhere to Alan Kay's notion of a 'message', it's clearly a structure (it's a complex one because it exposes a method). Components should communicate to each other via messages, not structures. the `mapDispatchToProps` idea has been in `react-redux` since the very beginning. I don't understand what are you complaining about here. If you prefer, you can ignore this practice and pass the whole dispatch function to the component, and then call `dispatch(actionCreator(params))` directly. Typescript won't get in the way of you doing that. > Also, the method builder.addMatcher(...) accepts a function as an argument - It doesn't seem like an ideal abstraction either. Functions are not messages. The addMatcher API is not concerned with sending messages, it is concerned with receiving them. You are specifying what should happen when a certain message type arrives. Hence it seems normal to me to specify that as a function that given the message does some stuff. Since these are reducers, the "does some stuff" part is: given a previous state and an action of type X, compute and return the new state Y. > Also, the thunkSendMessage function signature is very complex; the return type is highly convoluted. That's definitely not a message. Of course not. It is a function that sends some kind of message after asynchronously calling some API and getting its response, or another kind of message if the API call failed. That's what the complex type says. The usefulness of thunks is that you do have this notion of "ongoing asynchronous process that may succeed or fail" explicitly represented (so you can for instance cancel it), which you'll have a much harder time if you want to represent by using just actions and reducers. > You can already see the complexity seeping into the interfaces. Just a few years of TypeScript is distorting the original philosophy. I remember Dan Abramov was very careful about what to pass into functions and what to return from them and made it a point to encourage cloning objects using the ... spread operator. I think you are misremembering some things here. Dan Abramov used to advocate for a strict separation between presentational and container components [1], including the use of `connect` in exactly the same fashion you are criticizing in your first point. Likewise, cloning objects using the spread operator was never a goal in and of itself. Dan advocated for it as an easier way to avoid mutating the state. Not mutating the state was the point here. Redux-toolkit accomplishes this point by using immer, a library that conceptually gives you a copy of the state so you can just mutate and return that instead (with the advantage that non-modified parts are not cloned). Also, you can just not use that and return spread-operator-cloned objects from your reducer functions instead. Everything will keep working normally (and passing the type checks) in that case. [1] https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0 https://medium.com/@dan_abramov/smart-and-dumb-components-7c... PS: Thanks for your work Mark!
- matt_kantor 6y ago> I also miss the way that JavaScript encouraged people to define very simple function signatures (e.g. strings, numbers, plain objects/clones as arguments and return values). I wish this were true, and maybe it was in some cases, but not broadly. Many "types" used in JavaScript libraries are horribly complex. For example, the type signature of jQuery's `$` function, or moment.js's main function, or `server.listen` in Node.js, etc. In fact I think the opposite argument from yours could be made with a straight face: not having to declare types encourages more complexity. I don't really agree with that either, though; in my experience this is more a matter of developer discipline and API design skills. One thing I'm certain of is that if libraries are going to have complex types, I'd much rather have them be explicit and machine-verifiable vs hidden/implicit/hope-the-developer-wrote-a-good-docstring.