Functional programming has been around for a while, but it's really starting to gain traction. It's a different approach to writing programs instead of using object-oriented programming and it changes the way you think about problems and data. You focus less on how to do things and shift your focus to what things are. When you've been working in an object-oriented world, like most of us have, it can take a while to adjust to functional programming.
Although once you do adjust, it changes everything you thought you knew about a good program. You go from tracking down errors through crazy async calls where data types can change whenever they feel like it to methods that always return the same values when given the same data. Your code becomes close to bug-free and it's kind of crazy. We'll go over some background on functional programming, go through some examples in JavaScript, and then wrap up with some reasons you would want to use the functional approach.
Background on functional programming
The main goal of functional programming is to be able to reproduce values consistently, like you get with math equations. You want to know that when you put in data you will always get the right value back and functional programming accomplishes that. It uses a declarative approach to programming. Usually we describe the steps it takes to work with data instead of describing that data directly. Here's an example of the functional approach compared to the object-oriented approach.
Problem: get the total of a user's shopping cart
Object-oriented
Set the total variable to zero
put the price of each item into an array
Sum the prices in the array
Add taxes and shipping
Get total
Functional
The total of a user's shopping cart is the sum of the prices of all the items plus taxes and shipping
This is the core of the differences between functional programming and object-oriented programming. There are three main principles in functional programming that let us write code in this manner: immutability, separation of data and functions, and first class functions.
Immutability
Immutability handles complex issues like variable management. In object-oriented programming you normally assign values to variables and those values can change at any time. That can make it difficult to keep values in sync with the current state as your application grows to use thousands of variables. With all of those variables, it gets harder and harder to track down bugs.
Functional programming solves that problem by treating each variable like it is a value. It's not assigned a value, it is a value. For example say you have a user in your system and you want to give them new permissions. Normally you would do something like this.
let user = new User('contributor', ['view', 'write']);
user.addPermission('edit');
With functional programming you'll do something like this instead.
const user = {
role: 'contributor',
permissions: ['view', 'write']
};
const updatedUser = {
role: user.role,
permissions: […user.permissions].push('edit')
};
You'll notice that most variables are declared as const because of the immutability principle. This makes it so you can start with and keep an immutable initial data set, which means you have a definite single source of truth for your state. When you need to make changes to your data, you make a new variable that is that new value. That means every time you run through this code with that exact same data set you will get the exact same result.
Separation of data and functions
This is the trickiest part for people coming from an object-oriented background. In functional programming you have to keep your data separate from the code. No two-way binding allowed here. Instead of dealing with getters and setters and classes that reference other classes, you pass in the data you want your functions to work with. The data isn't included in the properties of a class where you have to manage the state of the properties.
You're working with a chain of constants that don't change the value of any data passed to it because of immutability. So if you're working with something like an array and you need to change a value, you make a copy of that array and make the updates to that. Here's an example of separation of data and functions in a simple budget tracker app in both the object-oriented way and the functional way.
Object-oriented
class PurchaseList {
constructor(purchases) {
this._purchases = purchases;
}
addPurchase(purchase) { /* do stuff */ };
}
class Purchase {
constructor(item, price, date) {
this._item = item;
this._price = price;
this._date = date;
}
getItem() {return this._item };
}
Functional
const purchase1 = {
item: 'toilet paper',
price: 12.47,
date: 2019-10-09
};
const purchase2 = {
item: 'plant food',
price: 10.87,
date: 2018-10-09
};
const purchaseList = [
purchase1,
purchase2
];
That's how data is separated from the functions from a code perspective. Functional programming deals mainly with arrays and objects in JavaScript, so make sure you are very familiar with the array and object methods.
First class functions
This is one of the more interesting parts of functional programming. You treat functions like any other data type. That means you can pass functions as parameters and return functions from other function calls. That brings up the concept of pure functions. A pure function is a function that doesn't depend on any state external to the function.
The only data a pure function has to worry about is the data that's being passed to it. When you have pure functions, the only way you will get a difference in the result is by passing in a different value. The returned result isn't effected by any data outside of the function. One of the goals of functional programming is to keep functions as pure as possible to avoid state management issues.
When the majority of your functions are pure, you can use those pure functions as "parameters" inside of other functions because you know that the pure functions are completely independent from everything else. We're going to make an example of a pure function and see how it's used when passed as a parameter.
Convert array example
To show how functional programming would work on something you might use, we'll go through an example by making a function that converts an array into something else. Say that you have an array full of unsorted, uncounted items for an e-commerce application. You want to return an object that has the name of each item and a count for each item to show a user. Here's how you would do that functionally.
const inventory = ['popsicle', 'underwear', 'sauce', 'pens', 'potatoes', 'sauce', 'onion', 'onion', 'pens', 'potatoes', 'ukulele', 'tomahawk', 'underwear', 'popsicle', 'sauce', 'ukulele', 'onion', 'underwear', 'popsicle', 'potatoes', 'onion', 'pens', 'ukulele'];
const countItems = inventory => {
return inventory.reduce((acc, name) => ({
…acc,
[name]: acc[name] ? acc[name] + 1 : 1
}), {});
};
What we've done here is made a function called countItems and it takes in an array called inventory. Then we use the reduce array method to turn this array into an object. Since the reduce method needs a starting point, we pass it an empty object as the second parameter for the method. Inside of the array, we use the spread operator on the acc variable to put the names and counts we have so far into the object we're returning.
Then we get the name that we are currently on in the array. We check the acc variable and if it doesn't have the current name in it yet, we'll initialize its count to 1. From there it goes through the entire array and keeps doing this check and count. There are a few things that make this pure function.
First you'll notice that there aren't any dependencies on external variables. Second, you'll notice that we used the spread operator instead of the actual acc variable. This keeps the immutability principle intact because we aren't changing the original variable. Lastly, the data is completely separate from the function. If you pass in the initial array multiple times, you'll always get the same result without worries and the same stands for any other array you pass in.
Reasons to use functional programming
Functional programming is a different approach to programming than object-oriented programming and it helps with a lot of problems OOP has. For starters, it helps you prevent almost all bugs and it makes your code way more readable. Because the results from your functions are always the same, it makes your applications more maintainable, dependable, and scalable overall. Another thing you don't have to worry about as much is state management because none of your functions or variables are as heavily dependent on state as they would be in OOP.
The way you have to think in the functional programming paradigm takes some getting used to if you come from an OOP background. Once you get used to it though, it'll be hard to go back to OOP because you notice all of the issues that functional programming solve. Your code is cleaner and it's just refreshing to not have unexpected changes in data.
What do you think? I really like the functional approach, especially for production applications. Have you had any experience, good or bad, with functional programming?
Hey! You should follow me on Twitter because reasons: https://twitter.com/FlippedCoding