When I first started learning about functional programming, I had a hard time wrapping my head around it. I understood the concept and the main principles but I lacked the practical knowledge. With this tutorial I want to cover not just the concepts but give you examples and show you how you can apply the functional programming paradigm on your own code.
Let’s first start by defining what is functional programming. Functional programming is a programming paradigm. Just like object oriented programming, functional programming has it’s own concepts. For example, everything revolves around being pure — functions always return the same output given the same input, they have no side effects, meaning they don’t alter or mess with any data outside of their scope.
It also advocates being immutable — once something is created, it cannot be changed. We may also often hear that functional programming uses a declarative approach opposed to the imperative approach that is also used by the object oriented paradigm.
These are just some of the concepts that makes up functional programming. But why are these principles important? What can they give us?
Why functional programming can benefit us?
It’s important to mention that functional programming is not a new paradigm. In fact, Lisp which was developed in the late 1950s was heavily functional. Still, we can benefit from it today for a couple of reasons.
One of them is that it will make your code easier to reason about. It focuses more on the “What is your program doing?” instead of “How does it do it’s thing?” — meaning you go with a declarative approach opposed to imperative implementations. To demonstrate, take a look at the two examples below.
In the first example, you focus on how the program is doing its thing, while in the second, you focus on what the program is doing:
The two implementations are doing the same thing, modifies an array so we have rounded numbers for each product. For this small example, it may seem like you are writing more code, but behind the scenes,
map will also return you a brand new array, meaning your original
products will be kept intact. This is immutability in action.
It also makes your code more easily testable as it focuses on small contained functions called pure functions. As mentioned before, these functions are deterministic, we can guarantee that if we keep passing it the same value, we get the same output.
In the end, functional programming makes your code easier to reason about. It makes it easier to read and follow the process you took and makes your application less prone to bugs. In case something still goes wrong, it’s easier to troubleshoot since your code is more concise.
To demonstrate how you can use functional programming in action, I’ve prepared some code examples that shows you how to be declarative.
Declaring what you mean
One of the best ways to start is by looking at array functions. Higher order array functions are a good example for the functional programming approach.
I have an entire article describing some of the array methods mentioned here, which you can check in the link below:
but let’s quickly go through some of the more important ones and see what they do and how they shorten our code to make it more readable.
Used for finding a specific element that passes the test, returns the first match
Used for returning the elements that passes the test, returns every match
If every element meets the criteria, it will return true
If at least one element matches the criteria, it will return true
Used for transforming an array, gives back a new one
Used for producing a single value from an array
You can already see how these array methods can shorten our code instead of using for loops, but we can make them even more powerful by chaining them. Most of these functions return an array, on which we can call another method and keep going until we get the desired result.
Function chaining is another great concept. It makes your code more reusable and again, reduces the noise and creates shorter, more concise code that is both more readable and in case of any bugs, it’s easier to debug.
In the example below, you’ll see that since each function call returns an array, you can keep calling new functions on them to create a chain.
Instead of using three different for loops to get the desired value, we can simply call functions one after another and get it done in 3 lines.
Last but not least, libraries can help you avoid writing down the same things over and over again — and reinventing the wheel — by introducing helper functions for commonly occurring problems.
There are many libraries out there that are following the functional programming paradigm, some of the more well known are Lodash and Ramda. To give you some visual differences between the two let’s take a look how you can retrieve nested properties in each — a commonly occurring problem. If one of the objects does not exist, we will get an error saying:
Let’s say we have a user object where we want to get their email address:
Lodash uses underscore
In each library, we can avoid getting an error if the parent of
Now you have a better understanding how to be more declarative. What are some other important concepts in functional programming? — It’s in the name, it is functions.
Functions in functional programming
There are many concepts how we can use functions to our own advantage. Let’s see some of the more commonly occurring definitions you can find in functional programming.
As discussed previously, pure functions don’t depend on any data other than what is passed into them and they also don’t alter any data other than what they returned.
To give you a practical example for pure functions, think of the
Math.random is impure since it always returns a different value, even if we were to pass it the same input.
Math.max however is a pure function since it will return the same output given the same input.
We need to note that in case our function doesn’t have a return value, it is not pure.
First class functions
Higher order functions
A higher order function is nothing more than a simple function that takes in another function as one of its arguments. Functions that return another function are also called higher order functions.
A great example for higher order functions are previously discussed array functions such as
Function composition is all about combining functions to form brand new functions.
For example, Ramda has the
compose function which takes in a list of functions as arguments and returns a function. You can call this with the input for which you want to apply the series of functions.
Currying is a technique where you call a sequence of functions with one argument instead of calling one function with multiple arguments. Each function return another function. The function at the end of the chain returns the actual expected value.
Recursion happens when a function keeps calling itself until some condition is met. In the example below, we are counting down from 100.
It’s important to specify an exit condition otherwise you will create an infinite loop that eventually crashes the browser.
Now if you feel like you are starting to become overwhelmed by the amount of information, don’t worry, it’s a good sign that means you are expanding your knowledge. There’s only two more important concepts we need to cover. They go hand in hand and they are immutability and side effects.
When we talk about immutable variables and objects, we simply mean that once declared, their value can’t be changed. This can reduce the complexity of your code and make your implementation less prone to errors.
To demonstrate immutability through an example, let’s say you have an array where you need to remove the first item. Take a look at the differences below:
In the first example, we modify the original array with the
shift function. If we want to achieve the same but keep the original array intact, we can use slice instead. This way you can avoid having unforeseen bugs in your application where you unintentionally modify data that should be kept in pristine condition.
One downside of immutability is performance. If you create too many copies you will run into memory issues so in case you operate on a large data set, you need to think about performance.
What are the side effects?
We also need to talk about side effects not because they are part of the functional programming paradigm but because they happen regardless what programming pattern you take. They are an important part of any program and you need to know when and why they happen.
So what are side effects? — Side effects can occur when a function is impure, therefore it’s not necessarily returns the same output given the same input. One commonly occurring example would be a network request. No matter what is the input, you can get back anything from 200 (OK) to 500 (Internal Server Error).
So you can’t avoid having side effects and your goal shouldn’t be to eliminate them entirely but rather to be deliberate, deliberate about why and when they happen.
Functional programming is a great way to organize your code in a better way. There are other programming paradigms out there like object oriented programming. So what should you use, which is better?
There’s really no answer, it depends on your situation and there’s no one above the other. You can also combine multiple paradigms together so it’s not a “one way or the other”.