tl;dr
redux
approximates the elm architecture w/o async capability. redux-loop
and elm-ts
both come close to exactly modeling elm, but redux-observable
is simplest & most powerful. redux-thunk
is naive, but popular enough that it influenced the design of redux
What's up with Redux?
redux
is super weird. Why would we architect an application that way? How did someone even think to write it like that?
redux
, redux-observable
and react
itself are the consequences of many concepts that have built on each other and in some cases been discovered in parallel. A couple of them come from backend design. Though each concept is simple, they form a complex web of relations to each other.
I gave up trying to come up with a coherent way to present this information, so I cheated. We're going to start at the present day and work ourselves backwards through the history of what led to redux
, redux-observable
and react
. Hopefully this is a useful way to present information that's not necessarily linear. If you're unclear or confused about a concept, just keep reading, I might explain it later.
This is an opinionated view of what happened. Please let me know if you disagree with anything or if I left out anything important in the comments below!
(I'm also putting this article out against a deadline, so I'll be going back in and fleshing out some parts of this over time)
Timeline Overview
- June 2020 - Halogen 5 Released
- June 2017 - fp-ts-rxjs Gives Observable a Formal Monad Instance
- March 2017 - elm-ts Ports the Elm Architecture to Typescript
- November 2016 - redux-pack Released
- May 2016 - Elm Abandons Strict FRP
- March 2016 - rxjs Version 5 Released
- Feb 2016 - redux-observable Released
- Jan 2016 - redux-loop released
- November 2015 - redux-saga Released
- July 2015 - redux-thunk Released
- June 2015 - Iterables, Generators Released as Part of ECMAScript 2015 (es6)
- May 2015 - redux is released
- May 2014 - Facebook open-sources Flux
- May 2013 - Facebook open-sources React
- March 2012 - Elm released
- September 2011 - Facebook Releases FaxJS
- Sometime Early 2010 (?) - Microsoft Releases ReactiveX
- February 2010 - Facebook Introuces XHP
- June 1997 - Conal Elliot and Paul Hudak Introduce FRP
- Late 1980s - Haskell Team Invents & Discards redux observable
- Sometime 1988 - CQS First Described by Bertrand Meyer
- Jan 1987 - Sagas First Described by Garcaa-Molrna and Salem
- December 1979 - MVC Architecture Introduced by Trygve Reenskaug
June 2020
Halogen 5 Released
Purescript Halogen gets its latest major release as of October 2020.
Halogen is a project initially created by Phil Freeman (original developer of the PureScript compiler) and funded by SlamData for use in their flagship app. So what is Halogen?
Elm and redux
have a problem - they use a single sum type used to effect changes on a single global state. This is a problem because there are conceptual and performance advantages to colocated state (analogous article in react here).
Halogen, which is a pure framework that uses a component architecture similar to react
, solved this problem by introducing sum types to handle communications between different components, called Query
and Output
.
The release of Halogen 5 introduces Action
, which handles internal changees to a component's state. Presumably Action
was named after redux's Action
. This means that each component has its own conceptual redux
, with three associated sum types:
-
Query
goes from parent -> child, e.g. a parent component telling a video component to start playing -
Message
(AKAOutput
) goes from child -> parent, e.g. a dialog box component might want to invoke a dismissal function specified by its parent -
Action
goes from child -> child, e.g. a clicked button makes an ajax request
There's also:
-
Input
which is analagous toreact
props
I mention this because Halogen is a logical extension of the ideas crystallized in redux
and react
. As such, redux
and react
can be simple frames through which to understand the complex Halogen architecture.
My theory is that Halogen
was so named because of its adjacency to Functional Reactive Programming - in chemistry, the Halogens are characterized by their reactivity.
June 2017
fp-ts-rxjs Gives Observable a Formal Monad Instance
Giulio Canti's fp-ts-rxjs
brings rxjs
into the fp-ts
ecosystem. It gives Observable
an official monad instance, which it also transforms with Either
for type-safe error handling and with Reader
for dependency injection.
Also, it adds fromIO
and fromTask
operators, effectively turning redux-observable
into an IO
entry point for fp-ts
programs using IO
or Task
.
March 2017
elm-ts Ports the Elm Architecture to Typescript
Giulio Canti's elm-ts
proves that the Elm architecture can be represented in terms of react
and rxjs
. This makes it a saner alternative to redux
+ redux-loop
.
The type signature of Cmd
reveals it's strange frankenstein nature:
export interface Cmd<Msg> extends Observable<Task<Option<Msg>>> {}
I view this as proof that redux-observable
is simpler and more powerful than the Elm architecture.
In fact, since elm can't represent higher-kinded types (typescript can by way of fp-ts)), elm-ts
is more powerful than elm itself.
November 2016
redux-pack Released
Only worth mentioning as an interesting approach to redux
async middleware. Though it came out after redux-saga
and redux-observable
, it's interesting to think of a Promise
of Action
as a stepping stone to the idea of a stream of Action
.
It was conceived because redux-saga
was seen as unwieldy. I would say that the type safety and simplicity of redux-observable
solves this problem without sacrificing any power.
May 2016
Elm Abandons Strict FRP
Elm gets rid of Signal
, favoring Sub
instead (short for 'Subscription'). Sub
is similar to Observable
, except with fewer combinators and operators. For more complex usage of Msg
, Elm keeps Cmd
, which it still uses at the time of writing.
In my opinion, by the very nature of the fact that Elm tries to model events, it was never truly FRP in the first place.
March 2016
rxjs Version 5 Released
rxjs
version 5, spearheaded by Netflix's Ben Lesh, is released. It's goals are to increase performance and stack safety.
Lesh collaborates with Microsoft, and has the type system in Typescript
had to be changed to accomodate rxjs
. He also collaborates with Google, who make rxjs
fundamental to Angular in their release of Angular 2.
Feb 2016
redux-observable Released
Netflix releases redux-observable
, which updates redux-saga
's pull-based Iterable
streams to more appropriate push-based Observable
streams.
While pull-based streams are great for handling back-presure, they make less sense for event handling. To paraphrase Ben Lesh (a principal maintainer of rxjs), "imagine every individual mouse event was represented as a js promise".
Since redux
is a front-end tool that handles events of one kind or another, a push-based architecture makes more sense.
It also provides a lush library of stream operators and combinators by way of Observable
and rxjs
. However, later this same month ixjs
will be released, providing similar operators and combinators for Iterables.
The name epic
is chosen to sound similar to yet different from saga
.
redux-observable
's logo is a circling loop colored similar to the ReactiveX
electric eel. It loops because of the recursive nature of epics
. They are three ducks because the Netflix team thought the word 'redux' sounded like 'three ducks'.
Jan 2016
redux-loop released
redux-loop
ports the Elm architecture to redux
as an async middleware.
While a noble effort, it's a bit of a mess. Check out the type signature for Cmd (it's too complicated and involved to reprint here.) redux-observable
's Epic
is far simpler.
November 2015
redux-saga Released
redux-saga
is an async middleware for redux
that uses the 'saga' pattern. From the home page:
The mental model is that a saga is like a separate thread in your application that's solely responsible for side effects.
Earlier this year in July, Caitie McCaffrey gave a talk about her usage of the Saga pattern to develop Halo 4 at Microsoft. This was the inspiration, in part, for redux-saga
.
July 2015
redux-thunk Released
The original async middleware for redux
, cited in its documentation.
redux
is so closely tied to redux-thunk
that it changed its type signatures to accommodate it.
Let's examine the useDispatch
hook in react-redux
. Here's how you might expect it to work:
import { useDispatch } from 'react-redux'
const Comp = () => {
const dispatch: (a: AppAction) => void = useDispatch<AppAction>()
return (...)
}
But here's how it actually works:
const dispatch: (a: AppAction) => void = useDispatch<(a: AppAction) => void>()
What's up with that type signature? Why don't we just have to prove our Action
type? Why do we have to provide the type signature for the dispatcher itself?
This is because with redux-thunk, it's possible to dispatch a function, so the type system has to be flexible enough to accomodate this. Here's an example of a function that might get dispatched (taken from the redux
docs):
export function fetchPostsIfNeeded(subreddit) {
// Note that the function also receives getState()
// which lets you choose what to dispatch next.
// This is useful for avoiding a network request if
// a cached value is already available.
return (dispatch, getState) => {
if (shouldFetchPosts(getState(), subreddit)) {
// Dispatch a thunk from thunk!
return dispatch(fetchPosts(subreddit))
} else {
// Let the calling code know there's nothing to wait for.
return Promise.resolve()
}
}
}
dispatch(fetchPostsIfNeeded('reactjs'))
The anonymous function returned by fetchPostsIfNeeded
is the titular 'thunk', although this is a misuse of the word - a 'thunk' is necessarily parameterless, since it represents a value that has already been 'thought through' and has no potential for change.
This is where redux
starts to show its age. Though it was developed to bring functional concepts to javascript, it existed in a time before static typing with Typescript became popular. Some of its behavior is weird, and the type system has trouble keeping up with it.
Similarly, the type for store is
June 2015
Iterables, Generators Released as Part of ECMAScript 2015 (es6)
es6
saw an expansion on es4's Iterators
with Iterables
. An Iterable
is a generalization of any pull based stream, pulled with the yeild
keyword.
Iterables are not necessarily asynchronous: Arrays
are Iterables too. This is part of the reason that reactive streams are not part of Functional Reactive Programming (FRP). A stream is by nature a sequence of discrete values, while FRP models continuous values as they relate to continuous time.
May 2015
redux is released
redux
comes into being as a way to reconcile Dan Abramov's package react-hot-loader with Facebook's Flux architecture. It is explicitly based on the Elm architecture. It's introduced a couple months later in a popular talk titled "Live React: Hot Reloading with Time Travel"
At the time, Elm used something called a Signal
. Signals composed together using a function called foldp:
foldp : (a -> s -> s) -> s -> Signal a -> Signal s
This has three parts: the a -> s -> s
is a function that takes an Action 'a' and the current state 's', and returns a new state 's'. The -> s
after that represents the initial state. This is all then used convert a signal from its Action to new state.
Abramov, seeing this first part a -> s -> s
recognized this as the function signature of es6 reduce
. fold
, when used on a product type in javascript, is called reduce
. This word reduce
, is the namesake of the reducer
, which has a similar function signature.
The word reducer
, combined with Flux
, is the namesake of redux
. The word 'redux' in English also means 'brought back, or revisited' - presumably, redux brings Flux back into the mainstream (?)
Part of the concept of reducers was that they'd be composable - a classically functional goal to be sure. Given that state is normally represented as a javascript object, each key of the object was to be given it's own reducer
import { combineReducers } from 'redux'
interface AppState {
user: User
current: Current
settings: Settings
}
const userReducer: (u: User | undefined, a: Action) => User = ...
const currentReducer: (c: Current | undefined, a: Action) => Current = ...
const settingsReducer: (s: Settings | undefined, a: Action) => Settings = ...
const reducer: (s: AppState | undefined, a: Action) => AppState = combineReducers({
user: userReducer,
current: currentReducer,
settings: settingsReducer
})
Notice that each sub-reducer can be passed in an undefined state, but must always return a value. This is because, in keeping with the compositionality theme, each reducer is responsible for initializing its own data.
Unintuitively, redux will propagate an action called @@INIT
through its reducer to collect this initialized state. In my opinion, this is a design flaw that can lead to unexpected behavior.
Despite all this, redux
is a beautiful system. In the talk, redux
is framed as bringing functional architecture to the mainstream. To quote:
These are not new things. I did not invent them. Some people will tell you that they existed 30 years ago, and maybe they'll be right, but we'll never find out if nobody talks about bringing cool functional techniques to javascript.
Because if you learn a cool programming language like Elm or Clojurescript, you stay there. It doesn't have to be this way.
You know, you can share your knowledge, please do. Because you can do functional in javascript. You can bring functional programming to the mainstream and do really cool stuff with it.
May 2014
Facebook open-sources Flux
Flux is facebook's implementation of MVC & CQRS on the client side. It's introduced in a talk called "Rethinking Web App Development at Facebook"
May 2013
Facebook open-sources React
In a surprisingly unpopular talk called "JS Apps at Facebook", Jordan Walke introduces react
to the public.
Given its name, it seems that react
came from 'Functional Reactive Programming'. However, the developers deny this:
The control over scheduling would be harder for us to gain if we let the user directly compose views with a “push” based paradigm common in some variations of Functional Reactive Programming ... There is an internal joke in the team that React should have been called “Schedule” because React does not want to be fully “reactive”.
March 2012
Elm released
Evan Czaplicki's Harvard undergrad thesis, named Elm, is released. The idea is to model a web UI using FRP in an accessible way.
The original paper documents different approaches to FRP & functional UI design through the 2010s. It's interesting to see how the ideas evolved over time.
It's easy to think that react
took major inspiration from Elm. Here's a passage in the introduction from Elm's original paper:
Functional reactive programming is a declarative approach to GUI design ... With functional reactive programming, many of the irrelevant details are left to the compiler ... The term declarative is important because most current frameworks for graphical user interfaces are not declarative ... FRP also encourages a greatly simplified approach to graphical layout.
Sound familiar? Here's a quote from the react.js homepage:
React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes ... Declarative views make your code more predictable and easier to debug.
Even if the name react
was never meant to refer to FRP, clearly the ideas here are similar, yet both radical departures from the norm.
It is not clear, however, that react
is at all based on Elm, as FaxJS
and XHP
have been in development for some time by now. I choose to believe that Elm
and react
were unaware of each other.
The two projects came to denotative design through radically different means: React from syntactic sugar by way of XHP, and Elm through academic rigor by way of frp. Like Hindley-Milner or Leibniz-Newton, the same concept was discovered twice. Phil Wadler, who brought monads into Haskell (building on Eugenio Moggi's work), often speaks of discovery as opposed to invention in mathematics in a similar way.
September 2011
Facebook Releases FaxJS
FaxJS is an early prototype of React.
var MainComponent = exports.MainComponent = F.Componentize({
structure : function() {
return Div({
firstPerson: PersonDisplayer({
name: 'Joe Johnson', age: 31,
interests: 'hacking, eating, sleeping'
}),
secondPerson: PersonDisplayer({
name: 'Sally Smith', age: 29,
interests: 'biking, cooking swiming'
}),
thirdPerson: PersonDisplayer({
name: 'Greg Winston', age: 25,
interests: 'design and technology'
})
});
}
});
You can see the denotative nature of the component architecture here.
Sometime Early 2010 (?)
Microsoft Releases ReactiveX
ReactiveX, originally developed by Microsoft for their .NET
system, models a stream called an Observable
and gives it combinators and operations inspired by their own LINQ
library. LINQ handles communications with databases. From their homepage:
Rx = Observables + LINQ + Schedulers
(Schedulers also exist in rxjs
. They control the nature of the concurrency of an Observable
)
Uses the same electric eel logo as the 2007 Microsoft project Volta because they were created by the same team.
Date inferred from here. Earliest doc found here, in June 2011.
February 2010
Facebook Introuces XHP
XHP allows the direct usage of xml inside of php files.
if ($_POST['name']) {
echo Hello, {$_POST['name']};
} else {
echo
<form method="post">
What is your name?
<input type="text" name="name" />
<input type="submit" />
</form>;
}
This syntactic sugar is the precursor to JSX, whose denoatative nature will eventually make react
so powerful.
June 1997
Conal Elliot and Paul Hudak Introduce FRP
At the International Conference on Functional Programming in the Netherlands, Conal Elliot and Paul Hudak introduce their paper titled "Functional Reactive Animation" (Fran for short). This idea will later be renamed Functional Reactive Programming (FRP).
Conal Elliot discusses the difference between FRP vs Reactive Programming in this SO post. FRP is by definition:
(a) denotative
(b) temporally continuous
Elliot also wrote a great post titled Why Program with Continuous Time, which is about pushing compromises to the boundaries of your system.
ReactiveX and FRP are fundamentally at odds: FRP's Behavior
is by definition continuous while Observable
is by definition discrete. FRP's Behavior
is by definition pure (implied by denotative) while Observable
is by definition effectful (thus fp-ts-rxjs's fromIO
). They both model streams, but in their intentions they are exact opposites.
In the same SO post, Elliot talks about FRP as it relates to rxjs
specifically:
As far as I can tell, Rx and Bacon.js lack both of the two fundamental properties on which I based the original FRP. In that sense, they're not what FRP set out to be. Which is fine with me, as I love to see a multitude of abstractions explored. Using a single term to describe them all, however, creates more confusion about what each means and how they differ. I think an accurate description of Rx and Bacon.js is "compositional event systems inspired by FRP".
It's a (really) bad example, but FRP as originally envisioned is closer to React Native's Animated
library than to rxjs
, Sagas or redux-observable
. Continuous time is represented by an Animated.Value
, and denotative transformations like Delay
and Spring
are applied before it's interpolated into actual positions at each frame. It doesn't look much like the Behaviors and Events described in the formal papers, but its continuous nature is closer in spirit than the discrete events output by Observable
.
Elliot has said that hearing people refer incorrectly refer to non-FRP systems as FRP is 'heartbreaking for me personally'.
The terms 'Functional Reactive Programming' and 'ReactiveX' are confusingly similar - 'ReactiveX' debatably uses a functional paradigm ('functional' is a poorly defined term), and they're both technically Reactive Programming. They are antonyms masquerading as synonyms!
Ben Lesh (lead maintainer of rxjs
) tweeted about this confusion - Elliot's reply:
Then perhaps better to discuss content first and names later.
Late 1980s
Haskell Team Invents & Discards redux observable
From Simon Peyton Jones's "Escape from the Ivory Tower"
A haskell program was simply a program from string to string ... This was a bit embarrassing. So after a bit we thought, maybe instead of producing a string, we'll produce a sequence of commands like "print hello," or "delete this file," which we can execute. So you can imagine, to run the program you call a program passing in some input and you get back a string of commands which some external command execution engine would - the evil operating system - the functional program is very good and pure and produces only a data structure, and the evil side-effecting operating system consumes these commands and executes them.
Well that's not very good if you want to read a file, because if you open a file and want to read it's contents, how does a program get access to it's contents? Ah! Maybe we can apply the function to the result of the evil external thing doing its work. So now there's this sort of loop between the pure functional program and the evil external operating system.
It didn't work very well at all. It was very embarrassing for a number of years.
Eventually, the solution that the Haskell team came to was the IO monad, as described in 1993's Imperative Functional Programming
Sometime 1988
CQS First Described by Bertrand Meyer
Command query responsibility segregation (CQRS) is a continuation of the principle proposed by Bertrand Meyer (1988) called command query separation (CQS)
Earliest doc I could find on CQRS is from Greg Young's November 2011 CQRS Documents
Greg Young says CQRS is basically MVC
Jan 1987
Sagas First Described by Garcaa-Molrna and Salem
Sagas relate to CQRS
December 1979
MVC Architecture Introduced by Trygve Reenskaug
Conclusion
From an engineering standpoint, short of implementing an undo history, redux-observable
is really a solution in search of a problem - it makes little sense. However, from a historical perspective, redux-observable
is the logical conclusion of attempts at a purely functional frontend architecture, achieved by way of streams, CQRS and the Saga pattern. Hopefully it's easier to understand where and when redux-observable
is an appropriate solution given a more whole understanding of its history.
I think deep understanding is a common difficulty in programming. React hooks are difficult to understand for many new developers. I would contend that it's easier to understand them if you have experience using setState
, and have a concept for react components as fundamentally object-oriented (unfortunately).
However, even given this understanding, it was initially difficult for me to truly wrap my head around hooks. Why are there 'rules' that are impossible to enforce at compile time? Why do we need an entirely separate library to test them? Why is that weird one called useEffect? And how do they actually work?
It helped to learn that react hooks are actually a vastly simplified implementation of algebraic effects. This explains so much to me - hooks are an extreme approximation, so by definition flawed and strange. Indeed, Dan Abramov of the react team has written candidly that for him 'much discussion about algebraic effects is incomprehensible'. I don't mean to advocate against hooks or criticize Abramov - I think some hooks can be super useful, and I don't fully understand algebraic effects either. I only mean to say that a wholesale understanding of hooks and the complexity of its origins helps understand how best to use them (minimally).
The point is that often, mere documentation is not enough. Interfaces are abstractions, and all non-trivial abstractions leak to some degree.
This is true on the backend especially. For example, Apache Kafka is almost impossible to understand without first understanding the solutions that preceded it.
A similar idea is explored in the paper What we Talk About When We Talk About Monads They discuss this in context of monadic solutions that ended up not being useful. The paper posits that programming concepts ought to be thought of across three different levels
... treating monads in a more comprehensive way and considering the formal, implementation and metaphorical level could have prevented the undesirable use of monads
The paper suggests that developers often eschew metaphorical explanations of monads as 'kludges', but such an understanding is crucial and fundamental. I would argue that most redux-observable
pedagogy is missing a different level of explanation.
In the spirit of the paper, we might say that the 'implementation' level of understanding redux-observable
is its interface - the type signatures for reducer
, dispatch
, and epic
. The 'metaphorical' level is the common understanding of redux-observable
as a 'state management' library - something that can help wrangle complex state. These explanations are not incorrect, but they miss the crucial third level - 'formal'.
Formally, redux-observable
is a referentially transparent functional architecture - an IO entry point. Its history is edifying because it took the long way to get there (as opposed to something like Elm). Understanding that journey can help understand some of the stranger points about it and some of the documentation and opinion pieces surrounding it.
Top comments (1)
Try it easy useSignal