Head-First Functional Programming in Javascript

Photo by Arno Smit on Unsplash

Head-First Functional Programming in Javascript

Learn the fundamentals of functional programming with practical examples

Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. JavaScript, being a multi-paradigm language, allows for functional programming along with object-oriented and imperative programming. Let's dive into some concepts of functional programming in JavaScript with examples.

Higher-Order Functions

In functional programming, functions can be treated as values and passed as arguments to other functions or returned from other functions. These functions are called higher-order functions. Here's an example

// Higher-order function example: map
const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map(x => x * x); // returns [1, 4, 9, 16, 25]

In this example, the map function is a higher-order function that takes an array of numbers and applies the provided function (x => x * x) to each element, returning a new array with the squared numbers.

Immutability

In functional programming, data is immutable, meaning it cannot be changed after it is created. Instead of modifying existing data, new data is created. Here's an example:

// Immutable example: updating an object
const person = { name: "John", age: 30 };
const updatedPerson = { ...person, age: 31 }; // creates a new object with the updated age

In this example, the person object is not modified directly. Instead, a new object updatedPerson is created with the same properties as person, except for the age property which is updated to 31.

Pure Functions

In functional programming, functions are pure, meaning their output is solely determined by their input and they have no side effects. Given the same input, a pure function will always produce the same output. Here's an example:

// Pure function example: add
const add = (a, b) => a + b; // does not depend on any external state and has no side effects
const result = add(2, 3); // returns 5

In this example, the add function is pure as it does not depend on any external state and has no side effects.

Now let's implement a small project to put these concepts into practice.

Now let's put all the learning into some practical world use case

Calculate Total Cost of Items:

we'll create a function that calculates the total cost of a list of items, including tax.

// Define the items
const items = [
  { name: "Item 1", price: 10 },
  { name: "Item 2", price: 20 },
  { name: "Item 3", price: 30 }
];

// Define the tax rate
const taxRate = 0.1;

// Define the function to calculate total cost
const calculateTotalCost = (items, taxRate) => {
  const totalWithoutTax = items.reduce((acc, item) => acc + item.price, 0);
  const totalWithTax = totalWithoutTax * (1 + taxRate);
  return totalWithTax;
};

// Call the function and print the result
const totalCost = calculateTotalCost(items, taxRate);
console.log(`Total cost: $${totalCost}`);

In this example, we're using higher-order functions like reduce, to sum up the prices of the items, and pure functions to calculate the total cost with tax.

Filter Even Numbers:

// Define the array of numbers
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// Define the function to filter even numbers
const filterEvenNumbers = (numbers) => {
  return numbers.filter(num => num % 2 === 0);
};

// Call the function and print the result
const evenNumbers = filterEvenNumbers(numbers);
console.log(`Even numbers: ${evenNumbers}`); // prints "Even numbers: 2,4,6,8,10"

In this example, we're using the filter higher-order function to create a new array containing only the even numbers from the original array of numbers.

These projects demonstrate some of the basic concepts of functional programming in JavaScript, including higher-order functions, immutability, and pure functions. By applying these concepts, you can write code that is more modular, maintainable, and less prone to bugs.

Composing Functions:

One of the key concepts in functional programming is function composition, which is the process of combining two or more functions to create a new function. Here's an example using arrow functions in JavaScript:

const add = x => x + 10;
const multiply = x => x * 2;
const subtract = x => x - 5;

// Compose the functions
const composedFn = (x) => subtract(multiply(add(x)));

// Call the composed function
const result = composedFn(5); // result is 20

In this example, we have three functions add, multiply, and subtract, each performing a different mathematical operation. We then compose these functions into a new function composedFn that applies these operations sequentially. The result of calling composedFn(5) is 20, as it applies the functions in the order add, multiply, and subtract on the input value of 5.

Currying:

Currying is a technique in functional programming where a function that takes multiple arguments is transformed into a sequence of functions that each take a single argument. Here's an example:

const add = x => y => x + y;

// Curry the function
const curriedAdd = add(10);

// Call the curried function
const result = curriedAdd(5); // result is 15

In this example, the add function is curried, resulting in a new function curriedAdd that takes a single argument y and returns the sum of x and y. This allows us to partially apply arguments to the curried function, creating new functions with some arguments already provided. In this case, curriedAdd is a function that adds 10 to any input value y, resulting in a result of 15 when called with curriedAdd(5).

Calculate Average Grade:

we'll create a function that calculates the average grade for a list of students.

// Define the array of students
const students = [
  { name: "Alice", grade: 95 },
  { name: "Bob", grade: 85 },
  { name: "Charlie", grade: 92 },
  { name: "David", grade: 88 },
  { name: "Eve", grade: 90 }
];

// Define the function to calculate average grade
const calculateAverageGrade = (students) => {
  const totalGrade = students.reduce((total, student) => total + student.grade, 0);
  return totalGrade / students.length;
};

// Call the function and print the result
const averageGrade = calculateAverageGrade(students);
console.log(`Average grade: ${averageGrade}`); // prints "Average grade: 90"

Here, we have an array of students, where each student object has a name and grade property. We define a function calculateAverageGrade that takes the array of students as input. Within the function, we use the reduce higher-order function to calculate the total grade of all students by summing up their grades. Finally, we divide the total grade by the number of students to get the average grade, which is then printed to the console.

This demonstrates the use of higher-order functions like reduce to perform operations on an array of objects in a functional way, as well as the immutability of data by not modifying the original array or objects. You can further expand this by adding more functionalities, such as filtering students based on certain conditions, or sorting students by their grades.

Mapping an array of numbers to calculate the square of each number:

const numbers = [1, 2, 3, 4, 5];

const squares = numbers.map(num => num * num);
console.log(squares); // prints [1, 4, 9, 16, 25]

In this example, we use the map higher-order function to create a new array by applying a transformation function to each element of the original array. This is a common functional programming pattern where we avoid changing the original array and instead create a new array with the desired results.

Filtering an array of objects to get only the items that meet a certain condition:

const books = [
  { title: "The Alchemist", author: "Paulo Coelho", genre: "Fiction" },
  { title: "The Lean Startup", author: "Eric Ries", genre: "Business" },
  { title: "The Catcher in the Rye", author: "J.D. Salinger", genre: "Fiction" },
  { title: "Start with Why", author: "Simon Sinek", genre: "Business" },
  { title: "To Kill a Mockingbird", author: "Harper Lee", genre: "Fiction" }
];

const fictionBooks = books.filter(book => book.genre === "Fiction");
console.log(fictionBooks);
/*
prints:
[
  { title: "The Alchemist", author: "Paulo Coelho", genre: "Fiction" },
  { title: "The Catcher in the Rye", author: "J.D. Salinger", genre: "Fiction" },
  { title: "To Kill a Mockingbird", author: "Harper Lee", genre: "Fiction" }
]
*/

In this example, we use the filter higher-order function to create a new array that contains only the objects that meet a certain condition (in this case, genre equals "Fiction"). This is another common functional programming pattern where we avoid modifying the original array and instead create a new array with the filtered results.

Implementing a memoized function for efficient caching of results:

const memoize = (fn) => {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
};

const factorial = (n) => {
  if (n === 0 || n === 1) {
    return 1;
  }
  return n * factorial(n - 1);
};

const memoizedFactorial = memoize(factorial);

console.log(memoizedFactorial(5)); // prints 120
console.log(memoizedFactorial(5)); // prints 120 (cached result)

In this example, we define a higher-order function memoize that takes a function fn as input and returns a new function that caches the results of fn for different input arguments. This can be useful for expensive computations or functions that are called multiple times with the same arguments, as it helps to avoid redundant computations and improve performance.

Let's see how some of the popular javascript libraries use this concept in their source code

Ramda (ramdajs.com):

Ramda is a popular functional programming library for JavaScript that provides many utility functions for working with data in a functional way. Here's an example from Ramda that uses pipe and map functions to chain together a series of operations on an array of objects:

const { pipe, prop, map, filter } = require('ramda');

const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
];

const getNameOfAdults = pipe(
  filter(prop('age', __, users)),
  map(prop('name'))
);

console.log(getNameOfAdults(users)); // prints ["Bob", "Charlie"]

In this example, Ramda's pipe function is used to create a function that filters the array of users by age, and then maps the resulting array to get the names of the adults. The functional composition approach using pipe allows for a more declarative and modular way of expressing the data transformation operations.

Redux (redux.js.org):

Redux is a popular state management library for JavaScript applications, commonly used with frameworks like React. Redux follows functional programming principles, such as immutability and pure functions, to manage the state of an application in a predictable way. Here's an example from Redux that demonstrates how actions and reducers can be implemented as pure functions:

// Action
const ADD_TODO = 'ADD_TODO';
const addTodo = (text) => ({ type: ADD_TODO, payload: text });

// Reducer
const initialState = [];
const todosReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, { text: action.payload, completed: false }];
    // other cases
    default:
      return state;
  }
};

// Store
const store = createStore(todosReducer);

In this example, the addTodo action creator is a pure function that returns an action object. The todosReducer is also a pure function that takes the current state and an action as inputs, and returns a new state based on the action. The use of pure functions and immutability ensures that Redux operates in a predictable and efficient manner.

In conclusion, functional programming is a powerful paradigm that can greatly enhance the way we write JavaScript code. By embracing concepts like immutability, higher-order functions, and method chaining, we can write more concise, modular, and expressive code that is focused on data transformation rather than managing mutable state. As demonstrated in the examples from popular libraries like Ramda, RxJS, and Lodash, functional programming can be applied in various real-world scenarios, including data manipulation, event handling, and asynchronous programming. So why not give functional programming a try in your JavaScript projects and unlock the full potential of this paradigm? Happy coding with functional programming!

Reach me out
Twitter

Github