Functional Programming Methods: Understanding Advanced JS Concepts

Functional Programming Methods: Understanding Advanced JS Concepts

Decorators

In JavaScript, a decorator is a function that can modify the behavior of another function or class. Decorators provide a way to add or modify functionality to an existing function or class without changing its source code.

Decorators are typically used to implement cross-cutting concerns such as logging, caching, access control, or performance optimization, which are applied to multiple functions or classes uniformly. Decorators can also be used to add or modify metadata to a function or class, which can be used for introspection or other purposes.

Decorators are implemented using higher-order functions, which are functions that take one or more functions as arguments and return a new function that wraps or modifies the original function.

let rectangleArea = (length, width) => { 
    return length * width;
}; 

const argCount = (fn) => {
    return (...args) => {
        if (args.length !== fn.length) {
            throw new Error(`Incorrect number of args`);
        }
        return fn(...args);
    }
};

const checkArgNumber = (fn) => {
    return (...args) => {
        args.forEach(arg => {
            if (typeof arg !== "number") {
                throw new Error(`Args must be number`);
            }
        });
        return fn(...args);
    }
};

rectangleArea = argCount(rectangleArea);
rectangleArea = checkArgNumber(rectangleArea);

rectangleArea(2,3) // 6
rectangleArea(2,3,4) // Uncaught Error: Incorrect number of args
rectangleArea(2,'3') // Uncaught Error: Args must be number

Decorators can also be used to modify class definitions. Here is an example of a decorator that adds a property to a class:

function greetDecorator(target) {
  target.greet = 'hello';
}

@greetDecorator
class MyClass {}

console.log(MyClass.greet); // hello

To use the @ decorator syntax in Node.js, we need to use a transpiler like Babel, which can transform our code to be compatible with older versions of Node.js and browsers that do not support the @ decorator syntax. Decorators are still an experimental feature in JavaScript and may change in future versions of the language.


Currying

Currying is a functional programming technique where a function that takes multiple arguments is transformed into a series of functions that take a single argument each. The resulting series of functions can be composed together to build up the original function.

In JavaScript, currying can be implemented using closures and function composition. Here's an example:

function add(x) {
  return function(y) {
    return x + y;
  };
}

const add2 = add(2);
console.log(add2(3)); // 5

In this example, the add function takes a single argument x and returns a new function that takes a single argument y and returns the sum of x and y. We then create a new function add2 by calling add with the argument 2. add2 is now a function that takes a single argument and returns the sum of 2 and the argument. Finally, we call add2 with the argument 3, which returns 5.

We can also use the bind method to implement currying:

javascriptCopy codefunction add(x, y) {
  return x + y;
}

const add2 = add.bind(null, 2);
console.log(add2(3)); // 5

Currying can be useful for creating reusable functions that can be partially applied with some arguments and reused in different contexts. It can also be used in function composition to build more complex functions from simpler building blocks.


Compose and Pipe

In functional programming, function composition is a technique for building more complex functions by combining simpler functions. Function composition involves taking two or more functions and combining them into a single function that performs the same task. Two common function composition techniques used in JavaScript are compose and pipe.

compose is a function that takes two or more functions as arguments and returns a new function that applies the functions from right to left. The result of each function is passed as an argument to the next function in the sequence. pipe is similar to compose, but applies the functions from left to right.

Example:

const compose = (...fns) => (arg) =>
    fns.reduceRight((prev, fn) => fn(prev), arg);
const pipe = (...fns) => (arg) =>
    fns.reduce((prev, fn) => fn(prev), arg);

// checking pallindrome strings
const pal1 = "Taco cat";
const pal2 = "John Doe";

const splitOnSpace = (str) => str.split(" ");
const split = (str) => str.split("");
const join = (str) => str.join("");
const lower = (str) => str.toLowerCase();
const reverse = (arr) => arr.reverse();

const fwd = pipe(splitOnSpace, join, lower);
const rev = pipe(fwd, split, reverse, join); // nested pipe

console.log(fwd(pal1) === rev(pal1)); // true;
console.log(fwd(pal2) === rev(pal2)); // false;

The code defines a few functions that are used to check if a given string is a palindrome.

  • splitOnSpace splits the string into an array of words, using a space as a delimiter.

  • split splits the string into an array of characters.

  • join joins an array of characters into a single string.

  • lower converts a string to lowercase.

  • reverse reverses an array.

Finally, the code creates two new functions using pipe:

  • fwd is a function that splits a string into words, joins them back into a single string (without spaces), and converts the result to lowercase. This is used to convert the input strings to a format suitable for comparison.

  • rev is a more complex function that first applies fwd, then splits the resulting string into characters, reverses the order of the characters, and joins them back into a string. This is used to create a reversed version of the input string that can be compared to the original.

Similarly, the two functions can be created by using compose :

const fwd = compose(lower, join, splitOnSpace);
const rev = compose(join, reverse, split, fwd);

console.log(fwd(pal1) === rev(pal1)); // true;
console.log(fwd(pal2) === rev(pal2)); // false;

Did you find this article valuable?

Support Zeeshan Ashraf by becoming a sponsor. Any amount is appreciated!