Sitemap

Writing Middleware —Composition and Currying Elegance in JavaScript

11 min readDec 5, 2016

Javascript is an elegant and beautiful language which allows the developers to have the flexibility to move between the world of imperative and functional programming. Most of the functional programming patterns and recipes are not so idiomatic to Javascript but it opens up a whole new world of possibilities by avoiding an enforced line of thought. Functional programming offers programmers to write shorter and tighter code to get to the point with significantly fewer lines of code. When we talk about functional programming the first thing we talk about is function composition. In fact function composition is regarded as a better alternative to object-oriented inheritance. But to understand function composition we need to first understand what pure functions are.

Pure Function

A pure function is a function where the return value is always deterministic, without any observable side effects. In other words, pure functions satisfy the following properties :

  • Given the same input, will always return the same output.
  • Produces no side effects — such as mutation of mutable objects or output to I/O device.
  • Relies on no external state — to produce result the function does not depend on any external resource.

e.g. Some pure functions

function add(a,b){
return a+b;
}
function getLength(str){
return str.length;
}
function Cos(x){
return Math.cos(x);
}

As pure functions are so independent, they are easy to reason about and refactor. They are the simplest reusable building blocks of code in a program which makes it easy to move the functions around the application and make the application adaptable to future changes.

Pure functions are the core of functional programming. The independent nature of pure functions removes the burden of dealing with the reasoning of shared mutable state. The power of pure functions can be highly appreciated when designing distributed systems with parallel processing as there are no mutations.

Understanding impurity makes it easy to achieve purity. What are impure functions?

  • Any function that uses a non-local variable is potentially impure.
  • Any function that causes mutations to the arguments passed on to it.
  • A function that returns the current time or date, is impure because at different times it will yield different results — it refers to some global state.
  • Math.random() is impure because each call potentially yields a different value.
  • console.log() and similar functions are impure because it causes output to an I/O device as a side effect.

Writing pure functions in functional programming languages like Clojure is very intuitive as they deal with persistent data-structures. But in Javascript when writing pure functions one has to fight the temptation of mutating the non-persistent data structures. If you are someone who doesn’t want to let go of the temptation, you can be rescued by immutable.js. Immutable.js provides many persistent immutable data structures which are highly efficient as it uses structural sharing via hash maps tries and vector tries as popularized by Clojure and Scala.

Let’s look into some preferred ways in Javascript to avoid mutations when dealing with non-persistent data-structures like objects and arrays when using immutable.js feels more of an overkill. Some of the methods mentioned below use new ES6 features which can be either polyfilled or used with transpilation libraries like Babel in case of unavailability.

  • Avoiding Array Mutations

Using map and filter

map and filter return a new array when applied upon an existing array which saves the existing array from mutation.

//map
[1,2,3,4].map(function(d){return d+1;}); //[2,3,4,5]
//Using ES6 Arrow Function :[1,2,3,4].map(d=>d+1); //[2,3,4,5] //filter
[1,2,3,4].filter(function(d){return d>2;}); //[3,4]
//Using ES6 Arrow Function :[1,2,3,4].filter(d=>d>2); //[3,4]

Using concat(), slice() and …spread

slice and concat return a new array whenever splitting or joining of arrays is required. Using a series of slice and concat we can even modify/insert/remove value at a specified index and still be able to get a new array. Using slice and concat works but it is messy, we can use ES6 …spread operator to make it concise.

var list = [1, 2, 3, 4];//Inserting value at the end of an array//Using push() - Impure
function insertWithPush(list, a) {
return list.push[a] //modifies the existing array
}
//Using concat() - Pure
function insertWithConcat(list, a) {
return list.concat([a]); //returns a new array
}
//Using ES6 ...spread operator instead of concat()
function insertWithSpread(list, a) {
return [...list, a]; //returns a new array
}
//Remove value at a specified index//Using splice() - Impure
function removeWithSplice(list, index) {
return list.splice(index, 1); //modifies the existing array
}
//Using slice and concat() - Pure
function removeWithSliceAndConcat(list,index){
return list.slice(0,index).concat(list.slice(index+1));
}
//Using ES6 ...spread operator
function removeWithSpread(list,index){
return [...list.slice(0,index),...list.slice(index+1)];
}
//Inserting a value at a specified index//Using ES6 ...spread operator
function insertAtIndexWithSpread(list,value,index){
return [...list.slice(0,index),value,...list.slice(index)];
}
  • Avoiding Object Mutations

Using Object.assign() and …spread

The Object.assign()method(ES6) is used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object. Properties in the target object will be overwritten by properties in the sources if they have the same key. Later sources’ properties will similarly overwrite earlier ones.

…spread operator can be used as an alternative to Object.assign() but is available only with ES7 (can be used with babel-preset-es2017)

As these operations result in a new object, mutations are thereby prevented.

var obj = {
foo: "foo",
bar: "bar"
}
//Adding a new key-value//Using obj[key] - Impure
function addToObject(obj, key, value) {
return obj[key] = value; //Modifies the existing object
}
//Using Object.assign() - Pure
function addToObjectWithAssign(obj, key, value) {
return Object.assign({}, obj, {
key: value
}); //returns a new object
}
//Using ...spread operator - ES7
function addToObjectWithSpread(obj, key, value) {
return {...obj,
key: value
}; //returns a new object
}

One nice thing about pure functions is that you can compose them into new functions.

For more detailed discussion on pure functions, one can go through this precious post by Eric Elliott https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976#.bm3rjd9c3

Function Composition

Function composition is the act of pipelining the result of one function, to the input of another, creating an entirely new function.

This can be done with any two functions, where the argument type of the first is the return type of the second. The newly created function takes what the second function would as a parameter and feeds it through the second function, then the result of the second function through the first function, and returns the result of the first function.

Mathematically, this is most often represented by the

operator, where

(often read as f of g) is the composition of f with g.

Let’s create a dirty little compose function which takes two functions and compose them to produce a new function.

function compose(f, g) {
return function(a) {
return f(g(a));
}
}
function add1(a){
return a+1;
}
function multiply5(a){
return a*5;
}
var add1Multiply5=compose(add1,multiply5);console.log(add1Multiply5(6)); //31

Analogous to compose there is one more familiar method attributed to unix is pipe which takes an array of functions to produce a new composed function.

const pipe = function(fns) {
return function(item) {
return fns.reduce(function(prev, fn) {
return fn(prev);
}, item);
}
}
function add1(a) {
return a + 1;
}
function multiply5(a) {
return a * 5;
}
var add1multiply5 = pipe([add1, multiply5]);
console.log(add1multiply5(6)); //35

It is interesting to see the difference in the return values.

If you are facing any difficulties understanding compose and pipe then revisit them after learning currying(explained later in the article).

Along with function composition there are some common techniques used with functional programming to make it clean, convenient and flexible. “Currying” is one of those techniques.

What is Currying ?

If you have come across the term “currying” and preferred to pass by it in silence assuming it is something only people living in ivory towers need to know, you can be forgiven. The concept of currying is a natural extension to the thought when a functional programmer asks this question —

If a mathematical function can only have one parameter, then how is it possible that a function can have more than one?

Well succinctly, currying is a process of converting a <n> arguments function into a chained sequence of <n> functions each with one argument. So what does it exactly mean ?

Let’s consider the following example;

function add(a,b){
return a+b;
}
add(1,2); //3

According to the above statement, we need to convert this function into a chain of two functions which take one argument each. Let’s do that,

function curriedAdd(a){
return function(b){
return a+b;
}
}
curriedAdd(1)(2); //3

This way of writing functions provides flexibility to create various types of add functions. e.g:

var add3 = curriedAdd(3);
var add5 = curriedAdd(5);
add3(2); //5
add5(2); //7

Currying is elemental in languages such as Haskell and Scala, which are inherently functional. It is so inherent that in Haskell every function is a curried function. JavaScript has functional capabilities, but currying isn’t idiomatic. Some functional JavaScript libraries such as Ramda have more flexible currying functions to create custom curried variation to any traditional function.

Here we won’t use Ramda but let’s create a small utility function that can convert any traditional function into a curried function. This function takes any traditional function and some arguments(to pre-populate) and returns a curried function waiting for the rest of the arguments a.k.a partial application.

function curry(uncurried) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
return uncurried.apply(this, args.concat(
Array.prototype.slice.call(arguments, 0)
));
};
};

To use this, we give it a function that takes any number of arguments, along with as many of the arguments we need to pre-populate it with. What we get back is a function that’s waiting for the remaining arguments:

function filterCollection(filter_fn ,collection) {
return collection.filter(filter_fn);
};
var greaterThan5 = curry(filterCollection,function(x){return x>5});
greaterThan5([3,4,5,6,7]); //[6,7]

After learning the bits about pure functions, function compostion and currying its time to use them to create a middleware. You might ask “What middleware?”. Well, do you recall the title of this post, which is something like “Writing Middleware…” :)

Feeling overwhelmed? Time to grab some coffee :)

Writing Middleware

According to the Wikipedia,

Middleware is computer software that provides services to software applications beyond those available from the operating system. It can be described as “software glue”.

Middleware makes it easier for software developers to implement communication and input/output, so they can focus on the specific purpose of their application.

As opposed to pure function, middleware is a function which contributes to adding necessary side-effects to an application flow such as making database calls, asynchronous API calls, logging, tracing, etc. Middlewares are usually applied as a list of functions which is analogous to function composition using pipe. Middleware acts as a glue between different parts of an application made up of pure functions.

To better understand middlewares let’s examine the functionalities of an Express.js (minimalist web framework for Node.js) middleware. Infact Express.js is a framework based on middlewares and routes.

Express.js Middleware functions can perform the following tasks:

  • Execute any code.
  • Make changes to the request and the response objects.
  • End the request-response cycle.
  • Call the next middleware in the stack.

If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function.

The middleware that we will be creating is heavily inspired by Redux middleware. This is conceptually similar to Express.js middleware.

Let’s consider the below object,

var account = {
calculateTax: function(amount) {
return amount * 0.1;
}
}

Suppose we need to perform some tasks like logging or calling external API to get the taxable amount whenever we make a call to account.calculateTax, as discussed above we would need some middlewares.

Let’s examine the most naive approach to perform logging,

console.log("Taxable Amount :", amount);
var tax = account.calculateTax(amount);
console.log("Tax Calculated :", tax);

But this manual approach is not flexible and doesn’t scale. We need to be able to chain as many middlewares we need in the flow.

Many of us might be tempted to use the infamous monkey-patching. Let’s give it a try,

//External API
function getTaxableAmountAPI(amount) {
return new Promise(function(resolve, reject) {
resolve(amount * 0.8);
});
}
//Patching GetTaxableAmount to account.calculateTax
function getTaxableAmount(account) {
let next = account.calculateTax;
account.calculateTax = function getTaxAmount(amount) {
getTaxableAmountAPI(amount).then(function(taxAmount) {
next(taxAmount);
});
}
}
//Patching Logger to account.calculateTax
function logger(account) {
let next = account.calculateTax;
account.calculateTax = function logAndCalculateTax(amount) {
console.log("Taxable Amount :", amount);
let tax = next(amount);
console.log("Tax Calculated :", tax);
return tax;
}
}
//Manually patching the account
logger(account);
getTaxableAmount(account);

account.calculateTax(1000);

The above code changes the account.calculateTax function everytime we try attaching one of our dirty middlewares.

Well this does the job, but it is difficult to live with something like this.

Let’s make it a little cleaner,

Instead of individually calling the patch functions we can write an applyMiddleware function which takes an array of middlewares. Also, rather than each middleware function changing the account.calculateTax function we can have them return the patched function.

function getTaxableAmount(account) {
let next = account.calculateTax;
return function(amount) {
getTaxableAmountAPI(amount).then(function(taxAmount) {
next(taxAmount);
})
}
}
function logger(account) {
let next = account.calculateTax;
return function(amount) {
console.log("Taxable Amount :", amount);
let tax = next(amount);
console.log("Tax Calculated :", tax);
return tax;
}
}
//Middleware applied in a sequence
function applyMiddleware(account, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
middlewares.forEach(function(middleware) {
account.calculateTax = middleware(account);
});
}
applyMiddleware(account, [getTaxableAmount, logger]);

It looks better than the brutal monkey-patching but account.calculateTax is still being patched. Let’s get rid of that.

Instead of getting ‘next’ from the account object if we curry one level deeper we would be able to pass the ‘next’ function into the middleware from the applyMiddleware utility.

function getTaxableAmount(account) {
return function(next) {
return function(amount) {
getTaxableAmountAPI(amount).then(function(taxAmount) {
next(taxAmount);
});
}
}
}
/* Moving a level deeper and passing in next from
the previous middleware */

function logger(account) {
return function(next) {
return function(amount) {
console.log("Taxable Amount :", amount);
let tax = next(amount);
console.log("Tax Calculated :", tax);
return tax;
}
}
}
/*Now middleware returns a calculateTax function, which in turn serves as next() to the middleware to the left*/
function applyMiddleware(account, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
let calculateTax = account.calculateTax;
middlewares.forEach(function(middleware) {
calculateTax = middleware(account)(calculateTax);
});
}
applyMiddleware(account, [getTaxableAmount, logger]);

These functions look really intimidating. Let’s use ES6 arrow functions to make them concise.

const getTaxableAmount = account => next => amount => {
getTaxableAmountAPI(amount).then(function(taxAmount) {
next(taxAmount);
});
}
const logger = account => next => amount => {
console.log("Taxable Amount :", amount);
let tax = next(amount);
console.log("Tax Calculated :", tax);
return tax;
}
function applyMiddleware(account, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()
let calculateTax = account.calculateTax;
middlewares.forEach(middleware =>
calculateTax = middleware(account)(calculateTax)
)
return Object.assign({},account,{calculateTax:calculateTax});
}

As in this case, the applyMiddleware function returns an object we can use it in the following way;

let account = {  calculateTax: function (amount) {    return amount * 0.1  }}account = applyMiddleware(account, [getTaxableAmount, logger]);account.calculateTax(1000);

This technique of creating middlewares can be extended to meet specific application design.

Reimagining application design with pure functions in JavaScript is yet to reach a wider acceptance. Using the concepts of pure function, function composition, currying/partial application and middlewares skillfully can alleviate a lot of design concern while making the application clean and concise. Functional libraries like Ramda and persistent immutable data structures library like Immutable-js greatly take away the burden of reimagination. Learning the concepts of functional programming is fun and inspiring. To further explore, fantasyland/fantasy-land is a great resource to understand the functional beauty in true sense.

I hope you enjoyed reading this article.

Explore-Learn-Dazzle.

--

--

Pragyan Das
Pragyan Das

Responses (3)