
Learn Functional Programming Design from Redux - daiyanze
https://pitayan.com/posts/redux-fp-design/?ref=hackernews
======
yakshaving_jgt
In my experience learning, teaching, and watching other people learn FP, it's
_far_ easier to learn FP with a language which is actually designed for it,
_e.g._ , Elm.

JavaScript isn't that. It carries too much legacy baggage. Too many gotchas;
too many pointy bits. It's also just super noisy.

There's a reason why someone had to write _The Good Parts_. I don't buy the
idea either that _modern_ JavaScript is somehow devoid of the kinds of
problems that existed when Crockford wrote that book. I mean, how many people
_still_ think that `const` gives you an immutable value?

 _n.b._ I'm not sure why the Haskell snippet was included. It's confusing, and
doesn't even make sense given that `main` is defined twice.

~~~
Osiris
This is my experience as well. I had a co-worker that really pushed for FP in
JavScript using Ramda.js. the principles were appealing, but because
JavaScript doesn't have native syntax for many of the principles like pattern
matching, you end up with hard to read code that's a bunch of nested arrays or
chains wrapped in helper functions like "compose([fn1, fn2])(data)".

I later tried to learn F# and while the syntax was foreign, it turned out that
it was MUCH easier to write FP code because the language had native operators
for composition, etc. The code was also much easier to read.

I think it would be great if JavaScript got some FP syntax natively (see the
pattern matching proposal). But hacking functional principals using arrays and
helper functions just leads to hard to read code, especially for people not
familiar with the specific FP library you chose to use.

~~~
adamkl
I'm actually thinking about putting together a series of posts on the topic of
"Full-stack ClojureScript for JavaScript developers". I've heard ClojureScript
described (from a functional programming standpoint) as "JavaScript, but
without the stupid parts", as it basically gives you the benefits of Ramda.js
+ Immutable.js without the headaches of making those libraries play nicely
with native JavaScript. It also doesn't veer too far into the theoretical
(monads, category theory, etc) so it would make for an easier transition when
coming from JavaScript (you can even throw exceptions if you want).

I became a Clojure fan-boy over the course of the last year but one issue that
seems to keep popping up when I discuss it with my JavaScript/Node.js co-
workers is "Clojure runs on the JVM? Not interested".

Setting aside the technical validity of that opinion (JVM vs. Node.js), it
made me wonder if there might be more interest in Clojure from the JavaScript
world if they knew they could use ClojureScript for both the front-end and the
back-end (on Node.js).

~~~
otsukare
I think I'm like your co-workers, I'm really interested in Clojure, but
admittably have that (probably unreasonable) JVM aversion. Do you mind sharing
why I shouldn't?

~~~
adamkl
The JVM is an excellent general purpose, multi-threaded runtime, with a large
ecosystem of open source libraries, and its well suited to many types of
"back-end" workloads, which is why it was the initial target for Clojure back
in 2006/2007\. Clojure's great interop with its host platform made it easy for
people to get up and running while leveraging all the existing Java libraries
they were used to.

The JVM is still a great choice for all those reasons, but its a little
heavier than Node.js which specializes in IO bound use cases (i.e. the back-
end-for-front-ends that sit between the browser and the heavyweight back-end
systems). Node.js is also probably better suited for smaller services,
lambdas, and other light-weight scenarios (IOT?).

I'm interested in promoting a full-stack approach to ClojureScript because the
JavaScript/Node.js ecosystem is everything that the Java ecosystem was 15
years ago. It has lots of developers, lots of libraries, and a "bad" language.
And by sticking with Node.js, its one less hurdle for JavaScript developers to
clear when learning ClojureScript. They still get to use all the same
libraries they did before (via ClojureScript's great interop), and the runtime
characteristics they are used to. Plus, if they then decide to start looking
at Clojure on the JVM in the future, they will have already learned the
language.

------
hasperdi
IMO Redux is a terrible way to start learning functional programming. There is
too much boilerplate code and other code (ie. reducer, selectors, side
effects, react components) that will confuse beginners and make the goal of
learning functional programming harder.

It's like telling someone who wants to learn to drive, here's the road laws
book, car manual, car service manual, offroad rally driving guide and engine
tuning manual.

~~~
Zealotux
Redux was my first introduction to FP, and I loved it. You simply have to use
Redux Toolkit that takes care of the ugly parts of Redux (namely: the
boilerplate code), and it becomes a breeze to build things with Redux.

------
diegof79
While I use Redux, and I like the simplicity of the single application state
and the reducer function. I don't see it as a good example of FP or even
JavaScript.

The problem with Redux is that it takes many patterns that make sense in
languages like Haskell or Elm but are not idiomatic in JS. For example, action
types and the usual reducer switch statement is easier to write and extend in
Haskell.

And is not idiomatic JavaScript either. It always baffles me that an action
dispatch is a way of doing a method dispatch (and yes I know that unlike a
method dispatch an action can be persisted, and broadcasted). But if you
compare the boilerplate and concepts introduced by a simple action dispatch
(action creator, action type, payload, thunk, middleware) with a simple method
call, and you look at the benefits (late binding with the state,
persistence)... I wonder why solutions like Zustand didn't come up before.

(note, I wrote state management code similar to Zustand for my personal
projects, but one of the reasons to continue using Redux is that getting the
Context updates right is hard... a problem solved by React-Redux with addition
of nice tooling and collaboration from core React devs. As soon React adds
fine-grained context state updates the reasons to continue using Redux for me
are low).

------
acemarke
Side note: I just published a brand-new "Redux Essentials" core docs tutorial.
It teaches beginners "how to use Redux, the right way", using our latest
recommended tools and practices, including Redux Toolkit for writing your
Redux code, the React-Redux hooks API for interacting with Redux in your
components, and use of single-file "slices" for a given feature's Redux logic.
I'd encourage folks to check it out:

[https://redux.js.org/tutorials/essentials/part-1-overview-
co...](https://redux.js.org/tutorials/essentials/part-1-overview-concepts)

My next step will be to rewrite the existing "bottom-up" tutorial sequence to
simplify explanations, remove outdated references, improve the explanation
flow, and add more running examples.

~~~
lf-non
I'd recommend caution when adopting redux-toolkit in large & rapidly-evolving
projects.

I recently (2 months back) moved a medium sized project (~240 branch reducers,
~600 actions) away from redux-toolkit.

My primary complaint is that the recommended setup doesn't work well with
changing requirements where we often have to move away from branch-local state
handling to something that needs access to wider state.

We started out with a number of slices and our reducer logic was local to
these slices and used only the state within that slice. However as our
application evolved, for processing a number of these actions (which were
previously slice-local) we needed access to state from other branches. So now
we had a couple of options:

A. Dispatch thunks instead of actions: This gets ugly real fast because now
your action handling logic is split across thunks & reducers, and is hard to
follow. This also needs refactoring across every dispatch site.

B. Use something like redux sagas to intercept actions: We found this to be
"too" flexible and felt that we were better-off without the entire machinery
of spawning sagas on the fly. We wanted it to be easy to reason about what
happens when an action is dispatched looking at the code without having to
debug what all sagas could be running at that particular point of time.

C. Move the action handling higher up: requires extensively refactoring the
reducers.

The solution we settled on was redux-loop [1]: A port of elm's effect system
to redux. This was neat because we could easily convert the reducer to return
loops instead of states, and thereby easily get access to full state and
dispatch while retaining the ability to follow through the complete action
handling flow from a single starting point that didn't change.

TypeScript support in redux-toolkit is also kind'a bolted on and users are
recommended different approaches when they care about type-safety. It proved
to be a pain to communicate junior devs multiple times that you should use
leave the reducers as empty object and instead use "extraReducers" with
builder API.

We found using immer[2] (for managing immutable state) and unionize[3] (for
handling discriminated union of action types) directly to be a much better
solution than redux-toolkit's abstractions.

[1]: [https://github.com/redux-loop/redux-loop](https://github.com/redux-
loop/redux-loop)

[2]: [https://github.com/immerjs/immer](https://github.com/immerjs/immer)

[3]:
[https://www.npmjs.com/package/unionize](https://www.npmjs.com/package/unionize)

~~~
acemarke
RTK doesn't change anything about how you write reducers and actions that need
to interact across slices. Our recommendations have always been the same:
reorganize slice reducers so they handle more state, put more data in actions,
or coordinate via side effects [0].

The only thing that changes with RTK is that we now recommend using the
single-file "slice/ducks" pattern for a given feature's Redux logic, and RTK's
`createSlice` API makes it easier to write code that's organized that way. If
you do have cross-slice dependencies, where slice A and B both want to respond
to each other's actions, that _could_ potentially lead to cyclic dependency
issues. Our RTK Usage Guide page specifically addresses that question [1], and
resolving it is generally a matter of defining the relevant actions in a
separate file again. But, having logic in a single file by default drastically
simplifies things in most cases. This issue isn't unique to RTK - it exists
any time you're trying to have different slices depend on each other,
regardless of how the logic in those files are implemented.

Also strongly disagree that RTK's TS support is "bolted on". RTK is written in
TS, and we've spent hundreds of hours trying to ensure our APIs work well with
TS [2]. We test against multiple TS versions, design our APIs to minimize the
amount of types you have to declare, and try to offer the best type safety
possible with our preferred API structure.

The only time you would ever define `createSlice.extraReducers` as an empty
object is if you are _only_ using that slice as a data cache and not adding
any additional client-side logic that would manipulate that cached data. In
that case, you'd probably be better suited to use `createReducer` directly.

Having said that, we are currently working on a PR to add the ability to
declare async thunks directly inside of `createSlice` [3], leveraging our new
`createAsyncThunk` API [4] so that their action types are automatically
generated to match the slice name and the action creator name you specify.
That will eliminate the need to call `createAsyncThunk` separately and pass
its actions to `createSlice.extraReducers`.

Finally, RTK has been built around Immer since day 1, and it's used in
`createReducer` and `createSlice` to allow you to write simpler "mutating"
immutable update logic.

If you have any additional specific concerns, please ping me on Twitter or in
the Reactiflux Discord. I'm always happy to answer questions and offer
suggestions, and I would really be interested in seeing some details on the
app you're working on to see if there's any ideas for improving RTK's APIs for
your kind of use case.

[0] [https://redux.js.org/faq/reducers#how-do-i-share-state-
betwe...](https://redux.js.org/faq/reducers#how-do-i-share-state-between-two-
reducers-do-i-have-to-use-combinereducers)

[1] [https://redux-toolkit.js.org/usage/usage-guide#exporting-
and...](https://redux-toolkit.js.org/usage/usage-guide#exporting-and-using-
slices)

[2] [https://github.com/reduxjs/redux-
toolkit/pull/393](https://github.com/reduxjs/redux-toolkit/pull/393)

[3] [https://github.com/reduxjs/redux-
toolkit/pull/637](https://github.com/reduxjs/redux-toolkit/pull/637)

[4] [https://redux-toolkit.js.org/api/createAsyncThunk](https://redux-
toolkit.js.org/api/createAsyncThunk)

------
wqsz7xn

      main :: (RealWorld ->) ((), RealWorld)
    

I've been doing haskell for a few months now. I don't think I have ever seen a
expression like this. It looks like State. Is this even valid syntactically?
I'm Probably bikeshedding.

~~~
yakshaving_jgt
The Haskell snippet included is basically gibberish. It’s best to ignore it.

~~~
tome
Has it been removed? I don't see any Haskell in the article.

~~~
dddbbb
It's still there as far as I can see, it's in an image[0]. I believe their
intention is to show a kind of pseudo-desugaring of what the IO monad actually
does internally.

[0] --
[https://pitayan.com/assets/static/haskell.cbab2cf.82d9f5b091...](https://pitayan.com/assets/static/haskell.cbab2cf.82d9f5b0912290333a547a32e17d3642.jpg)

~~~
tome
Ah yes, just goes to show I ignore images in articles!

