Functional programming is all about separation of concern which object-oriented programing does as well. but in functional programming, we don’t combine the data and functions in one piece or one object like with object-oriented programming. the functions operate on well-defined data structures like arrays and objects rather than belonging to the data structure like an object.
Side note: The idea of functional programming originates from mathematics, from something called lambda calculus. In JavaScript, libraries like redux and react have popularized the idea of functional programming.
Functional programming has an emphasis on simplicity where data and functions are concerned and we separate them, a separation between the data of the program and the behavior of the program.
All objects should be immutable which means once something is created it can’t be changed and most importantly prevent a shared state.
The goal of functional programming is the same as object-oriented programing:
- Clear and understandable
- Easy to extend
- Easy to maintain
- Memory efficient
- DRY principle
Pure Functions
Function the return the same output when given the same input and the function can not modify anything outside of itself, with no side effects.
Let’s see an example of a function that makes side effects:
const array= ['a', 'b', 'c'];
// The function mutate the array which defined outside of the function - side effect.
function removeItem(arr) {
arr.pop();
}
removeItem(array);
To fix it we should make a new array inside the function:
const array= ['a', 'b', 'c'];
// The function does not modify the array that defined outside - No side effect.
function removeItem(arr) {
const newArray = [].concat(arr);
newArray.pop()
return newArray;
}
removeItem(array);
function sum(num1, num2) { // num1 and num2 are local variables - no side effect.
return num1 + num2;
}
sum(10, 20); // Every time that we call sum with 10 and 20 it will return 30 - no side effect.
The main idea of functional programming is that pure functions are easy to test and easy to compose and they avoid a lot of bugs because we have no mutation and no shared state. We have these predictable functions that minimize the bugs in our code.
Side note: programs can’t exist without side effects. Interacting with the browser web API like the DOM and Fetch API are side effects. So our goal is only to minimize side effects.
Idempotent
A function that always returns or does what we expect it to do.
This is an example of a function that which we give the same parameter and it returns a different result:
function doRandom(num) {
return Math.random(num);
}
doRandom(5); // Random result.
doRandom(5); // Random result.
Imperative vs Declarative
Imperative code is code that tells the machine what to do and how to do it.
Declarative code is code that tells the machine what to do and what should happen.
An imperative is more like how the machines “think” and a declarative is more like how humans think. so being more declarative makes the code easier to read and understandable and makes us the developer more productive.
Side note: jQuery, for example, is more imperative than angular, react and view framework.
Let’s see an example:
// Imperative.
for (let i = 0; i < 1000; i++) {
console.log(i);
}
// Declarative.
[1,2,3].forEach(item => console.log(item))
Immutability
Immutability means not changing the data/state.
This is an example of code that mutate data:
const obj = {name: 'Nisan'};
function clone(obj) {
return {...obj}; // This is Pure.
}
obj.name = 'Dani"; // Mutating the Object.
Let’s see an example of how to fix it:
const obj = {name: 'Nisan'};
function clone(obj) {
return {...obj}; // This is Pure.
}
function updateName(obj) {
const obj2 = clone(obj);
obj2.name = 'Dani';
return obj2;
}
updateName(obj);
Higher-Order Functions and Closures
Functions are first-class citizens which means we can have higher-order functions and closures.
Higher-order Functions means that it’s a function that does one of 2 things: takes one or more functions as arguments or returns a function
The closure is a mechanism for containing state and it happens when a function accesses a variable defined outside of the immediate function scope.
// HOF
const hof = (fn) => fn(5);
hof(function a(x)){ return x };
// Closure.
const closure = function() {
let count = 55;
return function getCounter() {
return count;
}
}
Notice that we are not modifying count, so we do follow the rule of pure functions and functional programming paradigm.
Currying
Currying is when we modify a function that gets multiple parameters to a function that gets one parameter at a time. It is useful for creating multiple utility functions.
const curriedAdd = (a) => (b) => a*b;
const curriedAddBy3 = curriedAdd(3);
curriedAddBy3(10) // 30
Partial Application
It’s a way to partially apply a function. It’s a process of producing a function with a smaller number of parameters. it means taking a function, applying some of its arguments to the function, and when called again with the rest of the parameters.
Let’s see an example:
const add = (a, b, c) => a*b*c;
const partialAddBy9 = add .bind(null, 9);
partialAddBy9(7, 8); //15
Memoization
Caching is a way to store values for later use and by that speed our programs.
Memorization is a specific form of caching that involves caching that returns a value.
Here is an example of a function that accepts a parameter and makes long task processing with the parameter (simulation). If we will call the function again with the same parameter it will use the cache and will not do the long task processing.
let cache = {};
function memoizedAdding(n) {
if() {
return cache[n];
} else {
console.log('long task process');
cache[n] = n + 100;
return cache[n];
}
}
console.log(memoizedAdding(10)); // 'long task process' , 110
console.log(memoizedAdding(10)); // 110
Let’s improve this function since we pollute the global scope with the cache variable, we will use closure for that:
function memoizedAdding(n) {
let cache = {};
return function(n) {
if(n in cache) {
return cache[n];
} else {
console.log('long task process');
cache[n] = n + 100;
return cache[n];
}
}
}
const memoized = memoizedAdding();
console.log(memoized (10)); // 'long task process' , 110
console.log(memoized (10)); // 110
Compose and Pipe
Composition is an idea that any sort of data transformation that we do should be obvious.
composability is a system design principle that deals with this relationship of components,
Let’s create a compose function and use it:
const compose = (func1, func2) => (data) => func1(func2(data));
const multipleBy5 = (num) => num*3;
const makePositive = (num) => Math.abs(num);
const multipleBy9AndAbsolute = compose(multipleBy5, makePositive);
multipleBy9AndAbsolute(-30); // 150
A pipe is the same as composing, except instead of going from right to left, it goes left to right.
const pipe = (func1, func2) => (data) => func2(func1(data));
const multipleBy5 = (num) => num*3;
const makePositive = (num) => Math.abs(num);
const multipleBy9AndAbsolute = compose(multipleBy5, makePositive);
multipleBy9AndAbsolute(-30); // 150
These little functions are easy to test and pure but we are able to compose them together to do something more complex.
Arity
arity means the number of arguments a function takes.
In functional programming, although this is not a solid rule, it usually is a good practice that the fewer parameters there are the easier it is to use that function. the more parameters a function has, the harder it is to really compose it with other functions.