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, 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.
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).
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.
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.
The spread operator is crucial in helping us not mutate our state. What’s next?
Write declarative code
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.
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 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.
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.
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.
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.
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.
In most cases, any time you want to transform data into something else, you can use the reduce function.
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.
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.
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.
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.
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.
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.
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.
- 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.