This note refers to frontend reducers, and not to be confused with other reducers like from MapReduce.
Prior art
Although reducers can be represented as a simple switch case of events, the mainstream application of reducers happens either in React's useReducer
hook, or on Redux in which many of its qualities were motivated from Facebook's Flux architecture.
Along with Elm, the composition of these architectures are very similar to union types (called custom types in Elm) in algebraic data types (ADTs). Unlike normal state machines, we don't encode state in our ADT and assume the initial state of the reducer is the only state.
As a state machine
With regard to state management, reducers are essentially single state machines. Although dispatched events doesn't change the initial state, we expect the events to progress the data "context" of the machine. We can refer this as non-deterministic states. For instance, the non-deterministic state of the counter is the incremented value
:
We will use ReScript in our example to better represent our reducers as ADTs. In ReScript, the average reducer would look as such:
type state = int
type action = Increment | Decrement
export let transition = (state, action) =>
switch (action) {
| Increment => state + 1
| Decrement => state - 1
}
Its composition is very similar when we encode state and convert it into a state machine:
type state = Idle(int)
type event = Increment | Decrement
export let initial = Idle(0)
export let transition = (state, event) =>
switch (state, event) {
| (Idle(value), Increment) => Idle(value + 1)
| (Idle(value), Decrement) => Idle(value - 1)
}
Tradeoffs vs a regular state machine
Here a reducer has no concept of a "finite" state here, such that state can be represented finitely with a string. This is an inherent tradeoff that also gives us a useful advantage. Assuming the "context" or non-deterministic state of the reducer uses addition/multiplication, the reducer itself would follow the associative law. This gives us the benefit of converting any reducer that follows the associative law to parallelize its operations.