DEV Community

francesco agati
francesco agati

Posted on

Introduction to Functional Programming in JavaScript: High order functions #3

High-order functions and currying are powerful concepts that enable developers to write more modular, flexible, and expressive code. These concepts build on the principles of treating functions as first-class citizens and leveraging closures.

High-Order Functions

A high-order function is a function that either takes one or more functions as arguments, returns a function, or both. High-order functions are a cornerstone of functional programming because they allow for greater abstraction and code reuse.

Examples of High-Order Functions
  1. Functions as Arguments

    const numbers = [1, 2, 3, 4, 5];
    
    const filter = (arr, fn) => {
        const result = [];
        for (const item of arr) {
            if (fn(item)) {
                result.push(item);
            }
        }
        return result;
    };
    
    const isEven = (num) => num % 2 === 0;
    
    console.log(filter(numbers, isEven)); // [2, 4]
    

    In this example, filter is a high-order function that takes an array and a function (isEven) as arguments. The isEven function is applied to each element of the array to filter out the even numbers.

  2. Functions as Return Values

    const createGreeter = (greeting) => {
        return (name) => `${greeting}, ${name}!`;
    };
    
    const sayHello = createGreeter('Hello');
    console.log(sayHello('Alice')); // 'Hello, Alice!'
    console.log(sayHello('Bob'));   // 'Hello, Bob!'
    

    Here, createGreeter is a high-order function that returns a new function. The returned function uses the greeting parameter from its outer scope, demonstrating how closures are used in high-order functions.

  3. Built-in High-Order Functions

    JavaScript provides several built-in high-order functions, such as map, filter, and reduce.

    const numbers = [1, 2, 3, 4, 5];
    
    const doubled = numbers.map(num => num * 2);
    console.log(doubled); // [2, 4, 6, 8, 10]
    
    const evens = numbers.filter(num => num % 2 === 0);
    console.log(evens); // [2, 4]
    
    const sum = numbers.reduce((total, num) => total + num, 0);
    console.log(sum); // 15
    

    These functions enable concise and expressive operations on arrays, reducing the need for imperative loops and enhancing code readability.

Currying

Currying is a technique of transforming a function that takes multiple arguments into a sequence of functions, each taking a single argument. This allows for the creation of specialized functions from general ones and facilitates function composition.

Examples of Currying
  1. Basic Currying

    const add = (a) => (b) => a + b;
    
    const addFive = add(5);
    console.log(addFive(3)); // 8
    console.log(addFive(10)); // 15
    

    In this example, add is a curried function. The first call to add with argument 5 returns a new function that adds 5 to its argument. This demonstrates how currying can create specialized functions.

  2. Currying with Multiple Arguments

    const multiply = (a) => (b) => (c) => a * b * c;
    
    console.log(multiply(2)(3)(4)); // 24
    

    Here, multiply is a curried function that takes three arguments one at a time. This approach enables partial application of functions, where some arguments are fixed early, and the remaining arguments are supplied later.

  3. Practical Currying with Utility Libraries

    JavaScript utility libraries like Lodash provide convenient methods for currying functions.

    const _ = require('lodash');
    
    const add = (a, b, c) => a + b + c;
    const curriedAdd = _.curry(add);
    
    console.log(curriedAdd(1)(2)(3)); // 6
    console.log(curriedAdd(1, 2)(3)); // 6
    

    Using Lodash's curry method, we can easily transform a function into its curried form, allowing for flexible argument application.

Benefits of High-Order Functions and Currying

  • Code Reusability: High-order functions and currying promote code reuse by allowing generic functions to be easily customized for specific use cases.
  • Modularity: Breaking down functions into smaller, composable pieces enhances modularity, making code easier to maintain and understand.
  • Function Composition: High-order functions and currying facilitate function composition, enabling developers to build complex operations by combining simpler functions.

Top comments (0)