Taking a peek at functional programming with Redux
When building webapps with React, use Redux as a way to control communication between the components.
When developers build web applications with ReactJS, we often need a clear and organized way to control the communication between components. That’s why Facebook introduced Flux – an application architecture built to support unidirectional data flow.
In that same spirit, some new libraries sprang up from the React community since Flux’s release. Redux is one of the more popular implementations from this swell of activity. People love it for its minimal API, dependable unidirectional data flow which add great benefits to features like logging, hot loading, time traveling. Even Flux’s creators give high praise to Redux.
My experiences with Redux have been quite straightforward. Its official documentation provides excellent example code so that I was able to give it a quick try by following the patterns laid out for me. Be careful when using Redux for the first time because it’s methodology may feel a little obscure.
Map/Reduce Let us take a look at a simple Redux To-do app, written in ES6:
import { createStore } from 'redux'
// reducer function
function TodoApp(state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{ text: action.text }
]
case 'REMOVE_TODO':
return state.slice(action.index, 1)
default:
return state
}
}
// action creator
function addTodo(text) => {
return {
type: 'ADD_TODO',
text: text
}
}
function removeTodo(index) => {
return {
type: 'REMOVE_TODO',
index: index
}
}
let store = createStore(TodoApp)
// Dispatch actions, App state updates according to action type
store.dispatch(addTodo('Get up!'))
store.dispatch(addTodo('Have breakfast!'))
store.dispatch(addTodo('Go to work!'))
store.dispatch(removeTodo(0))
In the demo above, the reducer function ‘TodoApp’ is the core of the app store. The store holds the current global app state, so when you dispatch actions from anywhere in the app, they will pass through the reducer function to return an updated state.
Right now, the app state is as simple as:
['Have breakfast!', 'Go to work!']
In the real world, we would actually need to describe a to-do list with more details, such as if we need to distinguish completed to-do item from others, the app state object would have be like:
{
todos: [
{text: 'Have breakfast!', done: true},
{text: 'Go to work!', done: false}
],
highlighted: all
}
Accordingly, we will have actions to change the done
value of each to-do item and the action to change the highlighting status.
You see, unlike Flux, Redux utilizes a single store to maintain the whole state of your app, and it doesn’t have a dispatcher based on an event-emitter like Flux to dispatch the state change. Instead, Redux lets you write reducer functions to decide how each action transforms the app state.
Here someone may ask: is this what we call map and reduce? That’s exactly right. It isn’t strange to proficient JavaScript developers that these two features are supported by the native language API.
The Javascript array function map
is an example of iterating each item in the array and doing whatever transformations are needed for the data value or not:
['Alpha', 'Beta', 'Omega'].map((item) => {
return 'Hello ' + item
})
You’ll see that map
won’t do any structural changes to the array it iterates. Of course we can also archive the same goal with a for
loop, but it’s sort of verbose.
var names = ['Alpha', 'Beta', 'Omega'];
var tmp = [];
for (var i = 0; i < names.length; i++ ) {
tmp.push('Hello ' + names[i])
}
Another array function reduce
also traverses each array item but eventually transforms the array itself.
['Alpha', 'Beta', 'Omega'].reduce((prev, next) => {
return prev + ', ' + next
})
Again, if using a plain for
loop, it would be:
var names = ['Alpha', 'Beta', 'Omega'];
var tmp = '';
for (var i = 0; i < names.length; i++ ) {
if (i === 0) {
tmp = tmp + names[0]
} else {
tmp = tmp + ',' + names[0]
}
}
As its name implies, the reducer function in Redux is quite similar to the callback function the above Array.reduce(callback)
takes. Both functions have at least two parameters; one represents the previous result, while the other one represents the current value which needs to be combined to the previous result.
Map and reduce are commonly known functional programming features. For developers that aren’t very familiar with functional programming, any code implementation of that kind may seem unorthodox or even like black magic.
Functional programming feature in Javascript
Strictly speaking Javascript is not a real functional programming language, but some of its features allow us to borrow many functional programming techniques.
High order functions
We know in Javascript that when a function is labeled first class citizen
, it is treated as the object, which means it can be passed as a parameter for another function.
A typical example is that we pass in callback functions to an Ajax call.
For functions that can take functions as argument or return another function, we call this High order functions
.
With High order functions, we can do something called currying.
Currying
Currying is a technique that uses functions with partial parameters to generate another function that take the rest of the parameters. Suppose we need a function to add two numbers up, and it has to be in the form of:
fn(x)(y)
We could implement it as:
function CurriedAdd(x) => (y) {
return x + y;
}
We also could developed a generic way to convert a non-curried function to a curried one. One simple approach is:
function curry(fn, n) {
n = n || fn.length
return function curried(arg) {
if (n <= 1) return fn(arg)
return curry(fn.bind(this, arg), n - 1)
}
}
For above CurriedAdd
example, the traditional function would be like:
function add(x, y) {
return x + y;
}
curry(add) // convert add to CurriedAdd
There are more powerful and well-implemented packages out there for currying, such as dominictarr/curry nd lodash.curry. I highly encourage you to play around with them and take a closer look at their implementation details.
Compose
In object-oriented programming, we have design concepts like composition and aggregation to describe how we put together a list of objects to create a new one. With high order functions in Javascript, we could do similar things in a more flexible way.
Consider this:
var compose = (fn1, fn2) => (x) => {
return fn1(fn2(x));
}
The above function takes two functions as arguments then returns a new function that processes the input from the previous two functions.
Let’s make it more concrete:
var toUppercase = (str) => {
return str.toUpperCase();
};
var strongify = (str) => {
return '<strong>' + str + '</strong>';
};
var highlight = compose(strongify, toUppercase);
The beauty of compose is that we can compose more functions with a composed function:
var prefix = (str) => {
return 'Headline: ' + str;
};
compose(prefix, compose(strongify, toUppercase));
It looks like the composed function takes input data and pipes it through each function inside it, and since the operating order is from right to left, we can easily have the below equation:
compose(prefix, compose(strongify, toUppercase)) = compose(compose(prefix, strongify), toUppercase)
With this equation, we could implement an enhanced compose function to take a list of functions as arguments.
function enhancedCompose(arg) {
var n = arg.length;
if (n === 2) {
return compose(arg);
}
if (n > 2) {
return enhancedCompose(compose(arg[n-1], arg[n]));
}
}
longCompose(prefix, strongify, toUppercase);
We have seen a few basic functional programming techniques. Now let’s see how Redux applied them.
Functional programming in Redux
At the beginning of this article, I pointed out the connection between the Redux philosophy and the classic map/reduce technique. If we take a closer look at Redux’s implementation details, we can find the functional spirit behind Redux are more than just that.
bindActionCreators(actionCreators, dispatch)
In the previous To-do app demo, we need to invoke the dispatch
function from the store instance to use the action creator to change the state. The logic would be cleaner if we wrap up action creator to hide the reference to store.dispatch
.
var boundAddTodo = (text) => dispatch(addTodo(text))
var boundRemoveTodo = (index) => dispatch(removeTodo(index))
Redux provides a utility function bindActionCreators()
to do this binding process between actionCreator
and dispatch
. This is essentially an example of the compose
technique except for the order of passed parameters.
Middleware
Redux has support for middleware, which is able to carry out some tasks between when an action is fired and the reducer is called to update the state. In Redux’s official tutorial, it gives an example of logger middleware:
/**
* Logs all actions and states after they are dispatched.
*/
const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd(action.type)
return result
}
Clearly that middleware is example of curried functions.
Redux also provides the function applyMiddleware
to apply middleware and the final store instance would be:
let store = creatStore(rootReducer, applyMiddleware(middleware1, middleware2...))
In its actual implementation, what applyMiddleware
is:
let createStoreWithMiddleware = applyMiddleware(middleware1, middleware2...)(createStore)
let store = createStoreWidtMiddleware(rootReducer)
That’s currying again!
In the source code, we see a familiar face:
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
Here the compose
does exactly the same as the one I mentioned in previous section.
Immutability
Functional programming hates data mutation. Immutable data means we cannot change the data without change its reference. Aside from that it requires every functions to be pure. Pure function is, given the same input, the function always return same output. Pure functions cannot reply on any external environment so that it will avoid potential side-effect that causes by external changes.
// not pure
var a = 10;
function compare(x) {
return x > a;
}
//pure
function pureCompare(x) {
var a = 10;
return x > a
}
Redux also emphasizes these fundamental requirements in its statement about principles:
State is read-only The only way to mutate the state is to emit an action, an object describing what happened.
Changes are made with pure functions To specify how the state tree is transformed by actions, you write pure reducers.
Redux’s reducer functions don’t mutate any previous state data, instead it returns a new state object with same data structure. This immutability gives us performance gain when using Redux with React apps, as React assumes props and states of components are all immutable, so that it can prevent unnecessary re-rendering by efficiently comparing the references of previous props and states.
Functional programming with Javascript
To this point, I’ve tried to cover the rudimentary stuff about functional programming, but I’ve only skimmed the surface. Before you explore and experiment more with functional programming in Javascript, you might be interested to know a few pros and cons.
Pros:
Less code, more execution
Functional programming can save you from writing verbose imperative code as it strikes for making functions declarative.
Cleaner code structure and easier to maintain and test Functional programming concentrates on pure functions, so the data is loosely coupled with functions. Unlike traditional object-oriented programming, you don’t have to use abstract data with the object, so your essential job is writing functions. In this way, it’s much easier for code readers to see what problem the code solves, and it’s easier to do unit testing for each function by providing input data.
Cons:
Javascript is not a pure functional language like Lisp or Haskell, so it lacks native support for immutable data.
To avoid mutating a variable in Javascript when transforming data, we have to make a copy of the variable value and reference it. Especially when dealing with deep nested objects, there will be a lot of intermediate objects created in the deep copying process and waiting for garbage collection isn’t very good for performance. The upside here is that there are Javascript libraries like Immutatble.js and Mori.js to make the work less painful.
It has a certain level of complexity since it’s not as intuitive as object-oriented programming. This is a very opinionated point as the impression about functional programming varies from person to person. But I think, for now, object-oriented programming is still dominant in the industry for a good reason.