This is a two part series on the functional programming basics you should know before learning React and Redux. If you missed it, click here for part 1.
In the previous article you learned about functional programming and its benefits. The second article in the series is about how you write functional programmes. Before we continue, you might want to grab yourself a coffee and settle in. It's quite a long article!
Lets walk through the functional programming concepts again. Functional programming tells us we should avoid a few things…
- Avoid mutations
- Avoid side effects
- Avoid sharing state
These three are about not mutating our data aka aiming for immutability. We can achieve this by,
- Writing pure functions
Writing pure functions is the first tool you will learn. How else do we write functional programs?
- Write declarative code
This is about writing concise, readable code. This is a key concept in functional programming too.
- Be thoughtful about function composition.
This is about writing small functions which we can combine into bigger functions un till we have a full application. There a list of tools which we can use to compose our software, which have a broader term called higher order functions. We will go into detail on these as they are crucial tools in the functional programmers tool bet.
You will notice repetition of the above points are repeated through out the article to help drive them home. Let’s get into it… How do we write functional JavaScript?
Write pure functions
If we were to lend someone a book, we would rather that they didn’t make notes in it, and instead bought a new book and made notes in that instead. Pure functions have this idea at their heart. Pure functions returns the same value given the same input and do not mutate our data. When writing functions, you should try to follow these rules to ensure they are pure.
- The function should take at least one argument (the original state)
- The function should return a value or another function (the new state).
- The function should not change or mutate any of it’s arguments (it should copy them and edit that using the spread operator).
This helps ensure our applications state is immutable, and allows for helpful features such as easier debugging and more specifically features such as undo / redo, time travel via the redux devTool chrome extension.
In React, the UI is expressed with pure functions as you can see in the following code snippet. It does not cause side effects and is up to another part of the application to use that element to change the DOM (which will also not cause harmful side effects).
const Header = props => <h1>{props.title}</h1>
Spread operator (…)
The spread operator is an essential tool in writing pure functions and helps us ensure our application is immutable. See the below pure function. As you can see it’s copying the original array into a new one.
let colorList = [
{color: 'Red'},
{color: 'Green'},
{color: 'Blue'}
]
// The wrong way - aka colorList is mutated because we have pushed
// something into the existing array. It's also not declarative.
var addColor = function(color, colorList) {
colorList.push({color : color })
return colorList;
}
// The right way - aka colorList is immutable // and is declarative code.
const addColor = (color, colorList) => [...colorList, {color}];
We have pushed our data into a new array which is good!
Let’s look at another example, whereby we need to pull the last element from an array. Notice we are using ES6 destructuring to create our variables.
const numbersArray = [1,2,3,4,5,6]
const [lastNumberInArray] = [...numbersArray].reverse()
// 6
// We have created a new numbers array using the spread operator.
// We then reversed it so we can pull out what was the last number in the array.
// It would be the same as writing the below less declarative way.
const lastNumberInArray = [...numbersArray].reverse()[0]
The spread operator is crucial in helping us not mutate our state. What’s next?
Write declarative code
Writing code declaratively essentially means writing the least amount of code you can. If you’ve heard of 10x engineers, then they’ll be writing their code like this. The simplest way of understanding this is to take a look at the below example where we use the native JavaScript map function to achieve our goal in one line rather than three.
// imperative
const makes = [];
for (let i = 0; i < cars.length; i += 1) {
makes.push(cars[i].make);
}
// declarative
const makes = cars.map(car => car.make);
An example of the declarative nature of React is it’s render method. The below code renders a welcome message into the browser. It is a clean, simple way of writing something that would be very convoluted in without the help of the render function.
const { render } = ReactDom
const Welcome = () => (
<div id="welcome">
<h2>Hello!</h2>
</div>
)
render(
<Welcome />,
document.getElementById('target')
)
Declarative code is about writing code as concisely as possible and describing what should happen rather than how it should happen.
Thoughtful function composition
When you learn about functional programming you will read about the idea of composition. It involves ‘abstracting’ the logic as much as possible into small functions that focus on a specific task. These can then be composed into bigger functions un till you have a working application. Thoughtful composition will help keep our application more readable, maintainable and reusable. Below are the list of tools to help us compose our functions, starting with an explanation of a broader term for the group of tools, higher order functions.
Higher Order Functions
These are functions that are defined by their behaviour. Higher order functions either have another function passed in as an argument, or, return another function. This helps us achieve those desirable affects we noted in the first part of the series, e.g. easier debugging, more readable software etc. Think of higher order functions as Batmans utility belt which has a number of useful tools to help us write functional software. Those tools include,
- Map – native to JS
- Filter – native to JS
- Reduce – native to JS
- Recursive functions – we write our own
- Currying functions – we write our own
Note that map, filter and reduce return a new array and so are part of the tools that help us achieve immutability.
Map
Map applies a function to each element in an array and returns the array of updated values. The below example of the map function takes a list of colours, edits an existing colour, and returns a new list. Notice it’s achieves this in one line of code aka it’s declarative.
let colorList = [
{color: 'Red'},
{color: 'Green'},
{color: 'Blue'}
]
const editColor = (oldColor, newColor, colorList) => colorList.map(item => (item.color === oldColor) ? ({...item, color: newColor}) : item)
const newColorList = editColor('Blue', 'Dark Blue', colorList);
console.log(newColorList);
// [ {color: 'Red'}, {color: 'Green'}, {color: 'Dark Blue'} ]
As a bonus tip, we can use the map function to transform an object into an array. The example below shows how we can transform an object of book titles and their author into a more useful array.
const booksObject = {
"Clean Architecture": "Robert C Martin",
"JavaScript Patterns": "Stoyan Stefanov"
}
const booksArray = Object.keys(booksObject).map(key => ({bookTitle: key, author:booksObject[key]}));
console.dir(booksArray);
// [
// {bookTitle: "Clean Architecture", author: "Robert C Martin"},
// {bookTitle: "JavaScript Patterns", author: "Stoyan Stefanov"}
// ]
Filter
The below example of the filter function takes a list of members, creates a new list and removes the desired member so we have an up to date members list. If the function you pass in returns true, the current item will be added to the returned array and thus you have filtered your array. Also, note the reject function, which works inversely to filter.
const userList = [
{name: 'Bob', member: true},
{name: 'Fred', member: true},
{name: 'Keith', member: false}
]
const isMember = user => user.member === true
const members = userList.filter(isMember);
console.log(members);
// [{name: 'Bob', member: true},{name: 'Fred', member: true}]
// Notice how we have separated out isMember to its own function. This is declarative code and
// means we can reuse the function in the following way.
// Also, reject is just the opposite of filter.
const nonMembers = userList.reject(isMember)
console.log(nonMembers)
// [{name: 'Keith', member: false}]
Reduce
The third method is the reduce function. This is the ‘multitool’ and provides a more general function for when map and filter aren’t appropriate. The important thing to notice about reduce is that is requires a few more parameters than the others. The first parameter is the callback function (which also takes parameters) and the second parameter is the starting point of your iteration. It’s quite confusing at first but with a bit of practice and study you will begin to understand. Take a look at the below example.
var orders = [
{amount: 230},
{amount: 230},
{amount: 100},
{amount: 400},
]
const sumOfOrders = orders.reduce((sum, order) => sum + order.amount, 0)
// 960.
The 0 argument we gave as the second parameter of reduce() is passed into the first parameter of callback function, aka sum. The order parameter is the iterable, aka the order value.
It may also help to use the following parameter names to simplify your reduce functions, “result”, “item” and “index”. “result’ is the result you’re building up to in your reduce function, “item” is the current item you’re iterating over, and “index” is the index.
The above is a very simple example and doesn’t demonstrate the real utility of reduce. Another more complex version of reduce shows how we can create a new object out of an array of data. The below function creates a new array of users that are older than 18.
const users = [
{ name: 'Keith', age: 18 },
{ name: 'Bob', age: 21 },
{ name: 'Fred', age: 17 },
{ name: 'George', age: 28 },
];
const usersOlderThan21 = users.reduce((result, item)=>{
item.age >= 18 ? result[item.name] = item.age : null
return result
}, {})
// {Keith: 18, Bob: 21, George: 28}
In most cases, any time you want to transform data into something else, you can use the reduce function.
Currying functions
Currying is a function that holds onto a function which you can reuse at a later point in time. This allows us to break our functions down into there smallest possible responsibility which helps with reusability. Take a look at the below add function. It allows us to add two numbers together which is fine. But then, we realise that most of the time, we are adding 1 to our numbers, so we can use a curried ‘add’ function that can be used to create more specialised add functions such as add1 or add2. This helps with reusability and helps neaten your code.
const add = (a, b) => a + b
const a = add(0,1) // 1
const b = add(10, 1) // 11
const c = add(20, 1) // 21
// We can see we are adding one alot, so much
//we should abstract this further and make a curried function.
const curriedAdd = (a) => (b) => a + b
const add1 = curriedAdd(1);
const d = add1(0) // 1
const e = add1(10) // 11
const f = add1(20) // 21
// maybe we also want to have an add2 function?
const add2 = curriedAdd(2);
const g = add2(0) // 2
const h = add2(10) // 12
const i = add2(20) // 22
// as you can see we have a reuseable add function
// that we can apply as and where we need it.
Take a look at some of the other examples of where we can use currying. We can create a curried version of map, which allows us to create functions that can be run on an array, for example a doubleAll function.
// we can create a curried version of map which takes a function
// and maps across over it and returns a new function which
// will run our original function multiple times.
const arr = [1, 2, 3, 4];
const curriedMap = fn => mappable => mappable.map(fn);
const double = n => n * 2;
const doubleAll = curriedMap(double);
doubleAll(arr)
// [2,4,6,8]
Recursive functions
A recursive function is a function that calls itself, un till it doesn’t! It’s as simple as that. If it sounds like a for loop, then you’d be correct. You may choose a for loop when you only had one or two levels of recursion. The issue is when you have lots of levels of recursion, the for loop suddenly begins to become very unwieldy. The benefit of a recursive function, is that you can simply make a function call itself again and again till your rule is met. A recursive function can do what a for loop can, but in a much conciser way. In most cases you should use recursion over looping whenever possible. The example below shows how a recursive function can be used to count to 10.
// For loop
for (i = 0; i < 11; i++) {
console.log(i);
}
// 0, 1, 2, 3 ...
// Recursive function
let countToTen = (num) => {
if (num === 11) return
console.log(num)
countToTen(num + 1)
}
countToTen(0)
// 0, 1, 2, 3 ...
In this case, it may actually be more worth while simply using the for loop, as it is less code. If we consider a more complex loop you will see the real benefits of recursion.
Imagine we have an object which contains lots of data and we will need to access it’s values numerous times in our software. It would help if we had a function that could ‘pick’ the required data from whatever object we passed into it. In the example below, we code a recursive function called pick to help us handle this. See the comments in the code for an explanation.
let gus = {
animal: 'dog',
data: {
gender: 'male',
breed: 'Bull Dog',
info: {
color: 'white, brown',
behaviour: 'good',
mood: 'lazy'
}
}
}
// Lets see our recursive at work first. We pass in our object and field we want
// (using the standard javascript dot notation for picking values from objects)
// and it returns the value!
pick('animal', gus) // 'dog'
pick('data.info.behaviour', gus) // 'good'
// Now lets look at how we created our recursive pick function!
const pick = (fields, object) => {
// We split our fields string by the . and assign them to a variable
// using ES6 destructuing. Notice we use the spread operator
// because this doesn't care how many arguments it recieves.
// If we were to type ...remaining without the dots, it would
// return the second item in the fields array, which is no good for this function!
const [firstItem, ...remaining] = fields.split(".");
// we now have a variable called firstItem, which returns the
// first word of the string, and a variable which is an array
// that has the remaining words of the string in it.
// we can use a ternary statement to see if the remaining array has anything in it
// if it does we can run the pick function again
// if it doesn't we can get the value we want.
return remaining.length ?
pick(remaining.join('.'), object[firstItem]) :
object[firstItem]
}
Chaining functions
It’s worth remembering that functions can be chained together as well. This is another way that helps you to combine your smaller functions into larger ones. Typically for neatness, we drop the next function onto a new line as you will see in the below example, where we want to get all the even numbers from an array and double them.
const numbers = [1,2,4,5,7,8,9,10];
let isEven = (num) => num % 2 == 0
let double = (num) => num * 2
let doubleAllEvenNumbers = numbers
.filter(isEven)
.map(double)
Compose
Similar in the way we can combine smaller functions by chaining them together, we can merge them through a function that is commonly named compose(). Compose is a non native function to JavaScript and you can create it yourself as you can see from the below example. This helps with readability and maintenance.
// create our compose funciton
const compose = (...fns) => {
(arg) => {
fns.reduce(composed, f) => f(composed), arg)
}
}
// create our single responsibility functions
var sayLoudly = string => {
return string.toUpperCase();
}
var exclaim = string => {
return string + '!!';
}
// compose our single responsibility functions into a single one
var shout = compose(sayLoudly, exclaim);
exclaim('crumbs');
// crumbs!!
shout('crumbs);
// CRUMBS!!
Promises
JavaScript can only do one thing at a time as it is a single threaded programming language. If we needed to load some blog posts from an API we ideally wouldn’t want our whole page to have to wait for this information before loading. In the past, we used callback functions to handle but very quickly it landed us in ‘callback hell’, which was where you would have to nest numerous callbacks which ended up in very bloated code.
In recent years, ES6 has introduced Promises to deal with asynchronous behaviour. These are going to be integral to most software applications and so are required knowledge for the modern JavaScript engineer.
const getBlogPosts = (endpoint) => new Promise((resolves, rejects) => {
const api = `https://jsonplaceholder.typicode.com/${endpoint}`
const request = new XMLHttpRequest()
request.open('GET', api)
request.onload = () =>
(request.status === 200) ?
resolves(JSON.parse(request.response)) :
reject(Error(request.statusText))
request.onerror = err => rejects(err)
request.send()
})
const processBlogPosts = (postsJson) => console.log(postsJson.title, postsJson.body)
getBlogPosts('posts/1').then(
posts => processBlogPosts(posts),
error => console.log(new Error('Cannot get posts'))
)
As you can see, the promise function ‘promises’ it will ‘resolve’ or ‘reject’ your asynchronous function which you can ‘then’ act on depending on a success (the first parameter passed into then) or error (the second parameter passed into then).
You can also chain your promises together, by returning a promise within your promise. This allows you to wait for the first function to finish, then run the second, then third, and so on. This helps prevent race conditions in your code and will provide the help solve any asynchronous requirement in your software.
See the below example whereby the first promise returns another promise, which we chain onto with then(), and return another promise un till we are finished. We have also chained on a catch function, to catch any errors in the process.
new Promise((resolve, reject) =>{
setTimeout(() => resolve(1), 1000);
}).then(result =>{
console.log(result); // 1
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(result => {
console.log(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 2000);
});
}).then(result => {
console.log(result); // 4
}).catch(error => {
console.error(There's been an error', error)
})
We can make the Promise function even more declarative using the async / await functions. Lets convert our blog posts function to see how promises can become even more readable. Look at the example below where we have created a function called get getBlogPosts which returns a promise. We than then create an async function which can then await for the promise to be returned. We can use try to handle a successful response and catch to handle a failed response.
const getBlogPosts = (endpoint) => {
return new Promise((resolves, reject) => {
const api = `https://jsonplaceholder.typicode.com/${endpoint}`
const request = new XMLHttpRequest()
request.open('GET', api)
request.onload = () =>
(request.status === 200) ?
resolves(JSON.parse(request.response)) :
reject(Error(request.statusText))
request.onerror = err => rejects(err)
request.send()
})
}
const processBlogPosts = async (apiEndPoint) => {
try {
const blogPosts = await getBlogPosts(apiEndPoint);
console.log('Success', blogPosts)
}
catch {
console.error('Could not get blog posts')
}
}
processBlogPosts('posts/1')
//Success
// {title: "Blog Post title", content: "The content of the blog post"}
This method is more declarative and thus works well in our functional JavaScript applications.
Conclusion
Functional programming is a very useful style of writing code and has been used by React and Redux for good reason. If you know it well, it will make your life as an engineer much easier. Remember that it’s very easy to slip away from functional programming while writing JavaScript so you need to stay focused. The following few simple rules will help you stay on target.
- Keep data immutable.
- Keep functions pure (functions should take at least one arguments and return data or a function).
- Keep your code as concise as possible.
- Use recursion over looping (will help solve complex issues in a neater way).
That brings our series to a close. Hopefully you have learned what functional programming is and how it can be used to build better applications. If you are interested how Node (the server) and Mongo (the database) can be used with React and Redux to build out full applications, you can stay up to date by following me on the links below.
Happy engineering!
Make sure to follow me on twitter or dev.to for more tutorials to help with your journey into software engineering.
Top comments (3)
There are tradeoffs though...
this would be very inefficient with large arrays:
also, using recursion over big data structures can easily cause stack overflow in JS, especially in browsers where I don't think you can control its size
What an insightful article! It covers a lot of things and everything is well explained. It was very helpful.
Thanks so much Pratik, really glad it could be of help to you!!