Introduction to Functional Programming in JavaScript: High order functions #3
Tags: javascript
Published: 2024-07-05 22:00:00

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**

    ```javascript
    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**

    ```javascript
    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`.

    ```javascript
    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**

    ```javascript
    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**

    ```javascript
    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.

    ```javascript
    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.