10 Important Concepts in JavaScript Functional Programming for Enhanced Product Development | Part 1
Author : John Prabhu 25th Aug 2020
What is Functional Programming?
As the name suggests, functional programming involves the usage of mathematical functions. Moreover, all the functional programming correlates to mathematical functions in one way or the other.
Functional programming supports first-class functions such as assigning functions to variables, returning and passing functions as returned values and arguments respectively, to/from other functions. Also, it is a declarative paradigm that focuses on ‘what’ rather than ‘how’ and uses computing as the evaluation of mathematical functions. Moreover, you can avoid mutable state and side effects such as avoiding modification of state outside a function’s scope or interactions with its calling functions.
Functional programming also follows Lambda Calculus (λ-Calculus), which is a logic system that expresses computation based on function application and abstraction using variable substitution and binding. Additionally, functional programming can handle flow control with the help of composition of functions and recursion.
Functional programming is all about how you package your code as separate chunks in such a way each part is well-organized. When it comes to functional programming, data and behavior are separate things. Hence, they should be kept separate and the pillar of functional programming is pure functions.
What are Pure Functions?
Any function that follows the below points are pure functions:
Whenever the same input is given, the function should be capable of returning the same output regardless of the number of times it is being called.
It should not change the codes out of the functional limits. In other words, it should not create new bugs.
Therefore, let’s start off with Pure Functions and further elaborate about functional programming with examples of JavaScript programs.
function addition(num1,num2) { | |
return num1 + num2; | |
} | |
addition(3,4); // output is 7 regardless of the number of the number of times being called | |
function multiplication(num) { | |
return num * new Date().getTime(); | |
} | |
multiplication(6);// returns new output each time the function is called |
In the below example, we will be creating two functions removeLastItem and immutablyRemoveLastItem. The removeLastItem function affects the outer variable array whereas the immutableRemoveLastItem function doesn’t affect the outer variable. It is so because we employ concat method to make a copy of the external variable and process it to give the desired output.
let array = [3,5,7,11,13]; | |
function removeLastItem(process) { | |
process.pop(); | |
} | |
removeLastItem(array); | |
console.log(array); // output [3, 5, 7, 11] (this function changes the array variable from [3, 5, 7, 11,13] -> [3, 5, 7, 11]) | |
// The above program changes the array variable, which belongs to the outside world. | |
function immutablyRemoveLastItem(process) { | |
const newArray = [].concat(process); // concat method copies the value to new variable. | |
return newArray.pop(); | |
} | |
let newArr = [3,5,7,11,13]; | |
console.log(immutablyRemoveLastItem(newArr)); // output is: 13 | |
console.log(newArr); // output [3, 5, 7, 11, 13] -> the above function does not have side effect as it not modify the original input instead it copies |
Another simple example of pure function is illustrated below.
function addition(num1, num2) { | |
return num1 + num2; | |
} | |
addition (10, 20); | |
function multiplication(num) { | |
return num * new Date().getTime(); | |
} | |
multiplication(8); |
In the above example, we have created two functions addition and multiplication. Addition is a pure function as it doesn’t affect the outer variable after processing the function. But the multiplication function generates different output with respect to the current time.
Let’s look at some of the important terms in functional programming below.
1. Referential Transparency
Referential Transparency emphasizes that an expression in a JavaScript program may be replaced by its value or any other variable having the same value without changing the result of the program. As a result, methods should always return the same value for the given argument without any side effects.
Let’s understand this concept with a simple example.
let a = (num1,num2) => { | |
return num1+num2; | |
} | |
let b = (num) => { | |
return num*3; | |
} | |
console.log(b(a(6,4))) //output will be 30 | |
// here you can replace a(6,4) expression with value 10 and this will not effect to the result of the program because it returns the same value: 10 | |
// Hence you can replace console.log(b(a(6,4))) to console.log(b(10)) as the function is referentially transparent. | |
let c = (num1,num2) => { | |
console.log(`Value of num1 is:${num1} and value of num2 is:${num2}`); | |
return num1+num2; | |
} | |
console.log(b(c(6,4))) //output will be 30 | |
// here you cannot replace expression c(3,4) with value 10 as it affects the result of the program | |
// function c has console.log(), which is one type of side effect. So, the function ‘c’ is not referentially transparent |
In the above example, function ‘a’ is referentially transparent as it can be replaced by a value that is equal to the sum of the arguments without affecting the result of the program; whereas, the function ‘c’ is not referentially transparent because replacing the arguments with a value will affect the result of the program.
2. Idempotence
A function is said to be idempotent if it returns the same output for the same input or does what we expect it to do. Idempotence is different from pure function as it allows side effects. An example could be calling an API with an input and returning the same output no matter how many times it has been called. Another interesting feature of idempotence is the ability to call itself multiple times and still return the same output.
function notIdempotence(num) { | |
return Math.random(num); | |
} | |
notIdempotence(8); | |
notIdempotence(8); | |
// Regardless of passing the same values to the argument “num”, we get different outputs | |
function idempotent(num) { | |
console.log(num); | |
} | |
idempotent(8); | |
// The above function is idempotent because every time when we call it, it does the same thing for the same input. Hence, it is idempotent. | |
// Also, it is not pure because it alters the outside world as console.log will log in window | |
// another example | |
function absolute(x) { | |
return Math.abs(x) | |
} | |
absolute(absolute(absolute(-50))) //50 |
In the above example, we created 3 functions notIdempotence, idempotent, and absolute. Each time the function “notIdempotence” is called, it delivers different outputs and thus it is not idempotent. When it comes to the function “idempotent”, given the same input, it returns the same output, but it is not a pure function. In the “absolute” function, you always get the same output regardless of the number times the function is called within itself.
3. Imperative vs Declarative
Imperative code means what to do and how to do while declarative code means what to do and what needs to be done. It will not tell you how to do it. Let’s understand with an example
Imperative code is commanding how to do a certain task, whereas; declarative code means what to do. Here’s a simple example:
//Imperative | |
for(i=1; i<=10; i++) { | |
console.log(i); | |
} | |
//Declarative | |
[1,2,3].forEach(item=>console.log(item)); |
In the above example, we have written a program to log the numbers 1-3 using for loop and we defined only how to do it. But in forEach loop, it was declarative because we defined what to do. Functional programming helps us to be more declarative by using compose that tells our programs what to do instead of how to do it.
4. Immutability
Immutability means not to modify the original state by copying it and then applying required changes to the new state and returning the new state. Let’s see an example
let mutateObj = { | |
company_name:'TechAffinity', | |
location:'USA' | |
}; | |
let immutateObj = { | |
company_name:'TechAffinity', | |
location:'India' | |
}; | |
const mutatingState = (obj) => { | |
obj.company_name = 'TechAffinity Inc'; | |
return obj; | |
} | |
const immutatingState = (obj) => { | |
const copiedObj = Object.assign({},obj); | |
copiedObj.company_name = 'TechAffinity Global'; | |
return copiedObj; | |
} | |
/* | |
{company_name: "TechAffinity Inc", location: "India"} | |
company_name: "TechAffinity Inc" | |
location: "India" | |
}; | |
*/ | |
mutatingState(mutateObj); | |
console.log(mutateObj); | |
/* | |
company_name: "TechAffinity Inc" | |
location: "India" | |
}; | |
*/ | |
const newObj = immutatingState(immutateObj); | |
console.log(immutateObj); | |
/* | |
{company_name: "TechAffinity", location: "USA"} | |
company_name: "TechAffinity" | |
location: "USA"} | |
*/ |
Here, we have two functions: mutatingState and immutatingState. The function mutatingState changes the original state while the immutatingState function creates a copy of the original state and returns a new state. Functional programming recommends immutability because immutability provides stability and predictability to our code.
5. Higher-Order Function
In JavaScript, functions are first class citizens. First class citizens mean functions can be passed as an argument, functions can return a value, and functions can be assigned to variables.
What is Higher-Order Function?
A function, when received a function as an argument or a function whose return value is a function, such a function is termed Higher-Order Function. Let’s see with an example.
const hoFunc = function() { | |
return function() { | |
return 88; | |
} | |
} | |
hoFunc() // will return function | |
hoFunc()() // will return 88 | |
// hoFunc returns a function because it is a Higher-Order Function | |
const fn = function(x) { | |
return x*2; | |
} | |
const hoFunc2 = function(fn) { | |
return fn(8); | |
} | |
hoFunc2(fn); // will return 16 | |
// hoFunc2 accepts the function as an argument. Hence, it is a Higher-Order Function. |
In the above example we have two functions: hoFunc and hoFunc2. Since the hoFunc function returns a function, it is a Higher-Order Function. On the other hand, hoFunc2 accepts function as argument, which is again a Higher-Order Function.
Final Thoughts
These are highly critical especially when you’re running the IT Department of a startup. As a startup, you will need experienced and professional software developers from day 1 for quicker product development. Keeping the situation in mind, it makes functional programming a more attractive paradigm for your business. Above all, it is very important to consider the type of software you’re going to build. For example, if your product requires implementation of data science and further adds the requirement of a distributed computing platform for parallel processing to process large scale of data, you can go ahead with functional programming.
We, at TechAffinity, have developers who are familiar with both OOP and functional programming paradigms. Depending on the project requirement and the deadline, we strategically leverage the skill sets from them. Our experts are capable of handling the development of your product. Feel free to share your queries with us by sending an email to media@techaffinity.com or schedule a meeting with our experts.