Tuesday, March 12, 2019

Javascript Function Composition


Functional Programming is all the craze these days and we are seeing these concepts make its way into the JavaScript programming language. Although JavaScript is not a functional language by design there are some built in APIs that allow for a Functional coding paradigm. For example, the array methods filter, map and reduce allow for declarative, immutable transformations of arrays and objects and the const declaration allows for immutability of primitive types like string, boolean, and integer.

Function Composition

One interesting coding functional coding pattern is called Function Composition. If you use redux or express.js you will see this pattern being used to facilitate their middleware concepts. Middleware can be thought of chaining/pipeing a value through multiple stages before resolving it. For example, in express.js, we listen for a request, we then pass that request though multiple middleware methods like logging, authentication etc before actually sending it to the response event handler. In Redux we can use middleware for asynchronous handling of actions creators and for logging etc.

Functional composition is also a very interesting pattern to code out. In this post I'll explain how I created a library method that lets you pass in N number of middleware functions and it will compose/join it into a single function that will handle the pipe of your input through all your middleware functions before giving you a single entry point for your initial value.

/*
  this is a simple utility function that will compose/merge 2 functions. 
  It will be used in the main composeAll function when we recurse and process N number of function arguments. 
  It just takes 2 functions as "first" and "next" and returns a anonymous function
  that chains and passes along the "val" to both input functions.
*/
const composeTwo = (first, next) => (val) => {
  return next(first(val));
}

/*
  This is the main function. Comments are inline.
*/
function composeAll() {
  // arguments can be N, so convert them to a normal array using ES6 spread
  const args = [...arguments];
  const len = args.length;

  // if someone is composing nothing or just 1 function, deal with it here
  if (!args || len === 0) {
    return (val) => (val);
  } 

  if (len === 1) {
    return args[0];
  }

  /* using destructuring we pull out the first and 2nd item as "first" and "next" and spread out the remaining args into another array called "other" */
  const [first, next, ...other] = args;

  // if only 2 functions are left just compose them using composeTwo
  if (len === 2) {
    return composeTwo(first, next);
  }

  /* if more than 2 existed, use recursion to composeTwo the first 2 and then spread out the remaining
  "other" functions back into composeAll. Javascript recursion hooks onto to the call stack and therefore uses the stack datatype internally (last in first out)
  and we iterate this stack and end up eventually with just 2 functions, which get handled in the len === 2 block above */
  if (len > 2) {
    return composeAll(composeTwo(first, next), ...other)
  }
}

// Here are a collection of dummy "middleware" sample functions
function toUpperCase(val) {
  return val.toUpperCase();
}

function strongify(val) {
  return `${val}`
}

function pad(val) {
  return `----------${val}----------`;
}

function endSmile(val) {
  return `${val} :)`;
}

function startSmile(val) {
  return `:) ${val}`;
}

// composeAll all these middleware functions
const composed = composeAll(toUpperCase, strongify, pad, endSmile, startSmile);

// we then end up with a single "entry point". "foobar" goes through toUpperCase -> strongify -> pad -> endSmile -> startSmile -> in order
const applyAllToStr = composed('foobar');

// this will print ":) ----------FOOBAR---------- :)"
console.log(applyAllToStr);


If you want to use this feel free to grab the code from here : https://github.com/newbreedofgeek/jszilla/tree/master/packages/compose

Happy Coding!

No comments:

Post a Comment

Fork me on GitHub