Programming Language
JavaScript
Function
Functional Programming

Functional Programming in JavaScript

  • Pure Function + Side Effects + Immutability
  • Higher Order Function
  • Function Scope + Closure + Hoisting
  • Callback Function
  • IIFE (Immediate Invoke Function Expression)

Functional programming is a way of writing code where functions are the main building blocks. In JavaScript, functions can be used in many flexible ways, making it easy to write clear and reusable code. This guide covers important concepts like pure functions, higher-order functions, closures, and more, using simple language.

Function statement vs Function expression

// function statement - A function statement defines a named function.
function func() {} 
 
// Function expression - A function expression defines a function and can be anonymous or named. It's often assigned to a variable.
const myFunction = function () {};
 
// Fat Arrow function - Arrow functions are a shorter way to write functions and have a few special features, like not having their own this.
const myFatArrowFunction = () => {};

Pure Function and Side Effects:

A pure function always gives the same output for the same input and does not change anything outside the function. These functions are predictable and easy to test. If a function is not able to change any value of a variable, the function is called pure function. If input is same, output is same for forever. For example:

// Pure Function
function sum(a, b) {
	return a + b;
}
 
sum(10, 20); // 30

In this example, the sum function is pure because it always returns the same result when given the same numbers and doesn't affect anything outside the function.

A side effect happens when a function changes something outside of itself, like a global variable or the display. Side effects can make code harder to understand. Now let's talk about the side effect. If a function can update the value of a variable, it's called side effect. Examples are given below:

// Pure Function
let limit = 100;
function changeLimit(limit) {
	limit = 500;
	return limit;
}

This will not change the value of limit. That is why This is a pure function.

// Side effect
let limit = 100;
function changeLimit() {
  limit = 500;
}
 
console.log(changeLimit(limit)); // undefined
console.log(limit); // 500

Here, the changeLimit function changes the limit variable outside the function, which is a side effect. This will change the value of limit. So, it is the example of side effect. There are more examples are given below:

// Pure Function
const arr = [1, 2, 3];
function add(arr, data) {
	arr = [...arr, data];
	return arr;
}
// Side Effect
const arr = [1, 2, 3];
function add(data) {
	arr.push(data);
}
// Impure Function
function log(msg) {
	console.log(msg);
}

This function looks like a pure function, but it is an impure function. Because the console logs are side effects because they're logging out to the console. So if a function consists console logs there is a possibility that the function has some side effects.


Higher Order Function

A higher-order function is a function that takes another function as an argument or returns a function. These functions help us write more flexible and reusable code.

There are two condition for higher order function.

Benefit:

  • We can store func in a variable
  • we can store function inside an object / array
  • we can pass function as an argument
  • We can also return a function from another function

Example

  • Function can be passed as an argument.

    function randomSum(max) {
    	const random1 = Math.floor(Math.random() * max);
    	const random2 = Math.floor(Math.random() * max);
    	return random1 + random2; // placeholder
    }
     
    function randomSub(max) {
    	const random1 = Math.floor(Math.random() * max);
    	const random2 = Math.floor(Math.random() * max);
    	return random1 - random2; // placeholder
    }
     
    function randomSqrSum(max) {
    	const random1 = Math.floor(Math.random() * max);
    	const random2 = Math.floor(Math.random() * max);
    	return random1 * random1 + random2 * random2; // placeholder
    }

    There are many repetitive codes in the example. To follow the DRY (Don't Repeat Yourself) we can write the functions like this:

    function generateTwoRandNumber(max, cb) {
    	const random1 = Math.floor(Math.random() * max);
    	const random2 = Math.floor(Math.random() * max);
    	const result = cb(random1, random2);
    	return result;
    }
    const cb = function (rand1, rand2) {
    	console.log(rand1, rand2);
    };
    generateTwoRandNumber(100, cb);
     
    console.log(generateTwoRandNumber(1000, (rand1, rand2) => rand1 + rand2));
    console.log(generateTwoRandNumber(10, (rand1, rand2) => rand1 * rand2));
    console.log(
    	generateTwoRandNumber(10, (rand1, rand2) => rand1 * rand1 + rand2 * rand2)
    );

    Here generateTwoRandNumber() is a higher order function. Because it takes cb, a function, as an argument.

  • Function can be returned from another function.

    function power(p) {
    	return function (n) {
    		let result = 1;
    		for (let i = 1; i <= p; i++) {
    			result *= n;
    		}
    		return result;
    	};
    }

In this example, power is a higher-order function because it returns another function.


Hidden Concepts

Scope Key Concepts

Scope is where variables are accessible in your code. JavaScript has different types of scope:

  • Global Scope: Variables declared outside any function. These variables can be accessed from anywhere in your code.

  • Local Scope: Variables declared inside a function. These variables are only accessible within that function and any functions nested inside it.

  • Block Scope: Variables declared inside curly braces {}, such as in loops or conditional statements, when using let or const. These variables are only accessible within the block where they are declared.

There are some hidden concepts:

  • Scope

    • Global
      const a = 10;
      function mostOuter() {
      	function outer() {
      		console.log(a);
      	}
      }
    • Local
      function mostOuter() {
      	function outer() {
      		const a = 10;
      		console.log(a);
      	}
      }
    • Block
      {
      	const notScoped = 'scoped';
      	{
      		{
      			{
      				console.log(notScoped);
      			}
      		}
      	}
      }

A closure is a function that remembers variables from its surrounding scope, even after that scope has closed. Closures are useful for creating private variables or functions.

function outer() {
  const a = 10;
  return function inner() {
    console.log(a);
  };
}
 
const innerFunc = outer();
innerFunc(); // 10
 

Here, innerFunc remembers the value of a from outer, even after outer has finished running.

  • Closure: Closure is just a memory, which we can use after a function died.

  • Execution context

    function A(a) {
    	console.log('I am A');
    }
     
    function B() {
    	A();
    }
     
    function C() {
    	B();
    	B();
    }
    function D() {
    	C();
    	A();
    }
     
    D();

Hoisting is when JavaScript moves function and variable declarations to the top of their scope before code execution. This means you can use functions and variables before you declare them in the code. Note that only declarations are hoisted, not initializations.

  • Hoisting (Only applicable for var, not applicable for let and const)

    function randomSum(max) {
    	const random1 = Math.floor(Math.random() * max);
    	const random2 = Math.floor(Math.random() * max);
    	t();
    	function t() {
    		console.log(test);
    	}
    	var test = 'something';
    	t();
    	return random1 + random2; // placeholder
    }
     
    const r = randomSum(15);

Defining a Variable

There are two ways to define a variable.

  1. Globally
  2. Locally

Function Scope + Closure + Hoisting

Function Scope

Function scope means that variables defined inside a function are not accessible outside of it. This scope is created every time a function is invoked.

function myFunction() {
    let x = 10; // x is in the function scope
    console.log(x); // Outputs: 10
}
 
myFunction();
console.log(x); // Error: x is not defined

In this example, x is only accessible within myFunction and not outside of it.

Closures

A closure is a function that retains access to its lexical scope even when the function is executed outside that scope. Closures are created when a function is defined within another function.

function outerFunction() {
    let outerVariable = 'I am outside!';
 
    function innerFunction() {
        console.log(outerVariable); // Accesses outerVariable
    }
 
    return innerFunction;
}
 
const closure = outerFunction();
closure(); // Outputs: I am outside!

In this example, innerFunction is a closure that remembers the outerVariable from outerFunction, even after outerFunction has finished executing.

Hoisting

Hoisting refers to JavaScript's behavior of moving variable and function declarations to the top of their containing scope during the compilation phase. This means functions can be called before they are declared, and variables are initialized with undefined.

console.log(hoistedFunction()); // Outputs: Hello, world!
 
function hoistedFunction() {
    return 'Hello, world!';
}
 
// Variables are hoisted but not initialized
console.log(hoistedVar); // Outputs: undefined
var hoistedVar = 'I am hoisted!';

In this example, hoistedFunction can be called before its declaration because function declarations are hoisted. However, hoistedVar is only hoisted with an undefined value until it is assigned.

IIFE (Immediately Invoke Function Expression)

An IIFE is a function that runs as soon as it's defined. It's often used to create a private scope to protect variables from being accessed or modified outside.

(function (name) {
	console.log(name);
})('Pratap');
 
(() => {
	console.log('Test');
})();
  • Use case: We use IIFE to protect our variable from being accessed by anyone. By IIFE we can store our confidential variable.