DEV Community

Cover image for An Introduction to Functional Programming
Dave Howson
Dave Howson

Posted on

An Introduction to Functional Programming

Functional Programming has been around for many decades but languages like JavaScript have given it a new found popularity in the recent years due to it its simplicity and straightforwardness. Functional Programming is a Declarative Programming Paradigm where we tell a program what it needs to accomplish rather than telling it how it needs to accomplish this. For example, consider the following scenario using simple JavaScript.

Say we have an array of numbers [1, 2, 3, 4] and we need to add 1 to each of the numbers in this array. We can achieve this Imperatively or Declaratively.

The Imperative approach would be to first create a new array, run a loop through all the numbers in our initial array, add 1 to each of the numbers and push it to our new array.

const arr = [1,2,3,4]
const newArr = []

for (let x = 0; x < arr.length; x++) {
    newArr.push(arr[x] + 1)
}
Enter fullscreen mode Exit fullscreen mode

In the Imperative approach, we tell the JavaScript compiler exactly what needs to be done using low level constructs such as loops and assignments.

However, the Declarative approach would be to think about what our end result would be and achieve this without the use of conditionals and loops.

const arr = [1,2,3,4]
const newArr = arr.map(x => x + 1);
Enter fullscreen mode Exit fullscreen mode

The Declarative approach utilizes a method built into the JavaScript language to easily achieve what we want to without having to manually loop through the array.

Declarative Programming is much more cleaner than its Imperative counterpart in most cases and it leads to clean and minimal code.

Built on the concepts of Declarative Programming, Functional Programming is a paradigm where we create programs by applying and combining multiple functions together. In Functional Programming, a value can be converted into another value by running it through a set of functions.

const calculateValue = (val) => {
    const valByTen = multiplyByTen(val)
    const valPlusTwenty = addTwenty(valByTen)
    return valPlusTwenty;
}
Enter fullscreen mode Exit fullscreen mode

Above is a simple example of how a value can change as it is passed through multiple functions. Two important things to note here is that regardless of how many times you run this function, as long as the input value remains the same, the output value will always be the same as well. Secondly, the input value never changes throughout the function. Instead of changing it, we create a new value based on the input and return that as the result of the function.

Concepts of Functional Programming

In order to better understand Functional Programming, we must first understand the concepts and ideas behind Functional Programming.

First Class Functions

Languages such as JavaScript treat functions as First Class Citizens. This means that functions can be used anywhere that a regular entity (strings, numbers, etc..) are used. Functions can be stored in variables. They can be passed as parameters into other functions. Functions can be returned as a result of another function and they can also be included in data structures such as arrays. In order to write functional programming code, the language should support functions as first class citizens.

Another concept here is Higher Order Functions. When functions are treated as first class citizens, they can be passed as parameters into other functions. This helps us create complex programs by composing a set of simple functions.

Immutability

Immutability is the inability of an entity to be changed. In Functional Programming, we use Immutability to ensure that entities such as variables and functions always remain the same throughout the lifecycle of the programs execution. This concept helps us create predictable and maintainable code as we know that once a value is initialized, it will never be changed.

const name = 'john'

const upper = (value) => {
    return value.toUpperCase()
}

const newName = upper(name)
Enter fullscreen mode Exit fullscreen mode

In the above example, we assign the string john to a variable called name. When these lines of code are executed, the variable will be initialized with the given string and once done so, it can never be altered again. If we need to change the casing of this string to an upper case, we have to create a new variable called newName and pass the name as the parameter to the upper function which will create a new variable, convert it to upper case and return the result so that it can be assigned to newName. It is important to note here that name was never altered and thus, immutability was maintained. In the lack of immutability, code becomes harder to manage as we do not know which function changed the original value and that leads to unpredictable results.

Pure Functions

Functional Programming utilizes the concept of Pure Functions where given the same input to a function, it will always return the same output.

const multiplyByTen = (val) => {
    return val * 10
}
Enter fullscreen mode Exit fullscreen mode

The above function will always multiply a given input by 10 and respond with the new value. If we run this function with the same input, it will always return the same output. This concept is important because as our program and the functions in it grow in size and complexity, we must always maintain pure functions that return predictable outputs. In order to implement Pure Functions, we must always make sure that our functions have No Side Effects. A Side Effect is when a function alters a value outside of its local scope. We can make the above example an impure function by introducing a value outside of its scope.

let val = 10;
const multiplyByTen = () => {
    val = val * 10
}
Enter fullscreen mode Exit fullscreen mode

Since an outside value is altered by this function, we cannot ensure that its output will always be the same, thus making it an impure function.

Another important concept here is that of Referential Transparency. This means that the value of a variable never changes once defined. When we define a value and pass it as a parameter to a function, in a pure function, this initial value will never be changed. But in impure functions, like the one above, the initially declared value will change based on the execution of the function.

A pure function is predictable and its data will always flow through the same steps upon execution.

Functional Programming prevents the use of a shared state in programs because using such a state among functions would make them impure and unpredictable. However, there are workarounds for this where we can use a shared state which is the result of a set of data passed through a set of functions. Redux is a great implementation of this where we use a shared state in a functional way. You can read more about that here.

Using the above mentioned core concepts, Functional Programming helps developers create modular and predictable programs that are the result of composing multiple pure functions together. In Functional Programming, the output of the program is purely determined by its inputs and the functions that these inputs flow through.

Since this is an Declarative Programming paradigm, declarative concepts are applied in Functional Programming as well. For example, say that we are building an e-commerce platform and that we wrote a function to calculate discount based on the price and the type of item provided. Once written, we can use this function in the store section where we display products and their discounted prices. But we can also use this confidently in the cart section as well, where similarly, we need to show a product and its discounted price. In such a scenario, once we create the discount calculation function, we do not have to worry about its implementation any more. We can reuse this function in both the store and the cart because at that point, we know that since this is a pure function, given the same input, it will always return the desired and predictable output thereby allowing us to disregard its implementation logic (how the discount is calculated) and focus on where we can use it.

Advantages of Functional Programming

Functional Programming has been around for decades and its concepts have been used in Mathematics for much longer than that with good reason. Following are some of the advantages that programmers can gain from using Functional Programming.

  • Simple

    Functional Programming helps developers solve complex problems in a modular and simple approach. Instead of writing large and complicated solutions to solve a problem, we can break down a problem to more manageable parts and write functions to solve each of these problems individually. The final solution would be the result of all these functions working together to solve this complex problem using a modular approach.

  • Easy to understand

    Functional code is easier to understand because we only have to comprehend what is going on in a single function at a time. Even a program that solves a complicated problem can easily be understood by a programmer because this problem will be broken down to separate functions and to understand a single function, all you have to do is to identify the input parameters and figure out what this specific function is doing to alter the values that were passed into it. It also helps that these functions are not affected by anything other than the parameters passed into them.

  • Maintainable

    A large codebase can easily be maintained when all it consists of are individual functions. We can easily refactor any of these individual functions as long as we maintain its integrity and respect the inputs and outputs of it.

  • Easy to debug

    Simple, understandable and maintainable code is simply easier to debug. We do not have to think about the entire application and how it handles a complex flow of data in order to debug an issue in a single function. A function is given a responsibility to provide a predictable output and if it does not do this, then this is a problematic function which should be fixed. Doing so would not break other functions as the logic inside of a function does not affect these other functions. Only the inputs and outputs do.

  • Testable

    Functional code is very easy to test as these functions are inherently self contained. Once given some input, it should always return the expected output. It is very easy to write test cases for such functions and because of this, a functional codebase can be a highly tested and confident codebase.

Disadvantages of Functional Programming

As all things in life, Functional Programming too has its weaknesses that can be better solved using other paradigms such as Object Oriented Programming.

  • Complicated in some scenarios

    Simplicity is an advantage of Functional Programming, but in some scenarios, following the strict guidelines of Functional Programming can make simple solutions overly complicated.

  • Composing functions can be troublesome

    Functional composition, where we use the result of one function as the input of another function, thereby composing a large number of functions together can sometimes be harder to maintain. This depends on the problem domain and the way of approaching a solution. But just because Functional Programming can lead to cleaner code does not mean that it always leads to cleaner code.

  • Harder to comprehend

    Good Functional Programming is a result of good functional thinking. In order to breakdown a complex function to a set of simple functions, one must have to do a lot of thinking and planning. Programmers should also train themselves to think functionally which is not always an easy thing to do as our brains do not comprehend solutions that way.

  • Refactors are unavoidable

    Functional Programming depends heavily on the concept of reusable functions. However, in most cases, we simply cannot predict all of the potentially reusable functions that we will create, and in the long run, as we begin to better understand the solution we are building and as some of the requirements of our solution change, it will become inevitable that we will have to run across functions which will require refactoring.

With the advancements of the development ecosphere, everyday we tend to move more and more closer to simple and declarative solutions. Developers are moving away from the mentality of needing to custom-build everything from scratch and instead are starting to see the amazing benefits of reusing code and libraries across solutions. Programming languages such as JavaScript keep evolving and adding new features to it so that developers can focus more on the problems they need to solve rather than having to worry about the implementation details of the methods and tools they use. This landscape is breathing new life into the wonderful paradigm that is Functional Programming. For me personally, clean and reusable code is everything. I thank the day I decided to learn Functional Programming and I have loved it ever since. I hope you will too.

#happycoding

Discussion (0)