Functional Programming in JavaScript
I was trying to understand the Redux implementation and create my version of Redux(with basic functionalities)
For those who are not familiar with Redux, it is an excellent application-state management library for JavaScript applications.
I have used it along with my React projects, although it can be used just as well with vanilla JS applications Angular, Vue, etc.
If you have not yet used Redux, give it a try as it makes your application more predictable, maintainable, easier to test and not to mention the added benefit of Redux Dev tool that gives clear visibility of every state change (and time-traveling) .
Anyways, I digress....back to topic at hand.
JavaScript is a Multi-Paradigm Language
We have read this often in JS docs but what does it mean for JS to be a multi-paradigm language.
Programming languages fall in basically two categories - Imperative/Procedural and Declarative/Functional.
JavaScript supports both object-oriented programming(Imperative) as well as functional programming (Declarative).
Good reads on Imperative VS Declarative programming Declarative VS Imperative
Functional Programming Concepts
Let us explore some of the important functional programming concepts.
Functions are First Class Citizens
We have read this often but what does it mean ?
Simply speaking it means that functions can be
- assigned to a variable
- passed as an argument
- returned from another functions
const greet = () => { return 'Hello, '; }; const greetWithName = (fn, name) => { console.log(fn() + name); };
greetWithName(greet, 'Ayush!');
As clearly seen above, we are able to store functions and pass them as arguments in our code.
You might have encountered this as well when handling asynchronous callbacks during web development.
Let us simulate a callback after server response using setTimeout with one second delay.
const greet = string => { console.log(string); }; const delayedFunction = fn => (str, delay) => { setTimeout(() => { fn(str); }, delay); }; const delayedGreet = delayedFunction(greet);
delayedGreet('Wait and Greet', 1000); // logs "Wait and Greet" after 1 second
Higher Order Functions
Simplistically speaking, a Higher Order function is a function that accepts a function as an argument or it returns another function or both.
So, in essence, the setTimeout method or the map or the filter methods are all higher order functions.
// Filtering an Array for even numbers // Imperative approach const arr = [1, 2, 3, 4, 5, 6]; const even = []; for (let i = 0; i < arr.length; i++) { if (arr[i] % 2 === 0) { even.push(arr[i]); } } // Using Filter function const evenNumbers = arr.filter(item => item % 2 === 0);
We will also see shortly how higher order functions can be used to reuse code or abstract common logic.
Function Composition
The main essence of functional programming is to create smaller functions and then combine/compose them to solve a bigger problem.
In short, if there are two functions f and g then their composed function could be f(g(x)), evaluated from right to left.
// Calculate factorial of a number const fillNumbers = (a, b) => (a <= b ? [a, ...fillNumbers(a + 1, b)] : []); const product = arr => arr.reduce((item, total) => item * total); const factorial = number => product(fillNumbers(1, number)); const result = factorial(5);
In the above example
- fillNumbers methods takes two parameter a and b and returns an array between a and b (including a and b).
- The product method takes an array as a parameter and returns the resulting product of the numbers in the array .
- We can thus use these two functions to compute factorial of a given number.
Lodash also provides methods to compose functions to be used for functional programming.
import { compose, pipe } from "lodash/fp"; let input = " String With Spaces "; const trim = (str) => str.trim(); const lower = (str) => str.toLowerCase(); const wrapInDiv = (str) => `<div>${str}</div>`; const transform = compose(wrapInDiv, lower, trim); // read right to left const anotherTransformedString = pipe(trim, lower, wrapInDiv) // pipe transformations are read left to right
Currying
The number of parameter a function accepts is called its arity.
Currying is the technique by which we convert n-ary function ( a function which takes n arguments) into n-unary functions ( n functions with one argument) or same function with less arity.
This technique helps in partial application. So, even if we do not provide all the parameters to a function upfront, it will return us a function waiting for remaining parameters.
const sum = (x, y) => x + y; const curriedSum = x => y => x + y; // function takes x and returns a function which takes y as param const value = sum(2, 3); const addTwo = curriedSum(2); const anothersum = addTwo(4);
This technique is also used to create a higher order factory function
function log(date, message, user) { return `${message} logged at ${date} by user ${user}`; }
log(new Date(), 'Hello', 'ayush'); // Output: "Hello logged at 17:50:19 by user ayush"
//creating higher order factory functions function logCurried(date) { return function(message, user) { return `${message} logged at ${date} by user ${user}`; }; } const logNow = logCurried(new Date()); logNow('Hello Word', 'ayush'); logNow('JavaScript', 'raj'); logNow('Curry', 'AR');
In the above example, logNow method is the curried version. Here if you notice, we do not need to pass the date parameter again and the same date value will appear in all the messages .
Pure Functions
A pure function is one which does not depend on any external variable or state and always returns the same value for a given set of parameters.
const sum = (a, b) => a + b is pure as it is not dependent on any external variables.
const isMax = x => x > MAX_VAL ; This is not pure as it depends on an external/global state or value.
Similarly we cannot make any side effects/API calls or I/O operations inside a pure function
Immutability
Immutability is an integral part of functional programming. Functional programming encourages not to mutate data.
const employee = { name: 'Niks', address: { city: 'Mumbai', country: 'India', }, }; let updated = { ...employee, name: 'Nishant', }; updated.address.country = 'Singapore'; console.log(employee.address.country); // Singapore /** Because internally, both object share the same in-memory 'address' object. This requires deep copy **/ updated = { ...employee, address: { ...person.address, country: 'USA', }, }; console.log(employee.address.country); // Singapore ( now employee does not change after changing adress of updated object
Thinking in terms of Functional Programming
We are used to thinking in terms of Imperative programming (at least this holds true for me). It takes a while to start thinking in terms of functional programming.
Why should you bother ?
Once you start writing your code by following the principles of functional programming, you will realize that it makes your code more concise, easier to read and test.
It is also in line with how our mind normally thinks.
For example, let us see this through code.
We have a data set of districts which shows how many doctors and nurses have been vaccinated. The task is to find out how many health workers (doctors and nurses) have been vaccinated for district 'X.
const town = [ { id: 1, place: 'A123', doctors: 123, nurses: 234, district: 'X', }, { id: 2, place: 'B123', doctors: 333, nurses: 344, district: 'Y', }, { id: 3, place: 'C123', doctors: 242, nurses: 134, district: 'X', }, { id: 4, place: 'D123', doctors: 332, nurses: 234, district: 'X', }, { id: 1, place: 'E123', doctors: 433, nurses: 542, district: 'Z', }, ]; const vaccinatedInX = town .filter(location => location.district === 'X') .map(location => location.doctors + location.nurses) .reduce((acc, location) => acc + location);
console.log(vaccinatedInX)
The output above is obtained by chaining the three methods (filter, map and reduce).
Using filter we filter out the data for district 'X', then create a new array which contains the sum of vaccinated doctors and nurses. Finally we do the sum of the numbers in the array using the reduce method.
As you can see from above, declarative programming is more about what to do without explicitly writing statements describing what to do.
Lead Engineer
4yLove this
Full Stack (MERN) Web Developer
4yThanks for posting
Senior Front End Engineer
4yNo problem. Thanks for the article. I'll look for more from you. If you want to go deeper on functional programming, try porting your example to use reduce again, with transducers. An interestimg take on Functional programming. If you are thinking of recreating redux, how about using RXJS. Something to google and consider.
Senior Front End Engineer
4yInteresting article. Can I make one suggestion. At the bottom, instead of creating 3 intermediate arrays and iterating over them each time, when chaining array methods, it's best to just use Reduce and combine into one iteration, with only the total returned.