Skip to Main Content

Functional Programming Principles with JavaScript

Michael Pardue, Accusoft Senior Software Engineer

A lot of the tools and techniques that developers learn in other languages do not translate well. Mocking, dependency injection, code generation, and test coverage all work a little bit differently. Indeed, this means that how one tackles the structure of code needs to change to best grasp the benefits of JavaScript while avoiding the pitfalls.

One of the major benefits of JavaScript lies in the ability for developers to use functional programming techniques.

 

Why Functional Programming?

Functional programming is a paradigm where computation is performed through expressions, avoiding state change and mutable data. This leads to safer and provable code. A function that is stateless and has no side effects is easy to test and validate. A code base that is easy to validate instills greater confidence in the product and ultimately a stronger willingness to release more and more frequently.

Now, JavaScript is by no means a pure functional language. “Pure” simply indicates that the function has no side effects; there are a number of properties that can be derived from that, but that’s for another blog post. The language allows choices that facilitate safer code converging on pure functional programming without sacrificing the practical aspects of non-functional code.

In JavaScript, side effects really are the bane of the developer’s existence. Losing track of the scope of a variable can lead to unexpected and difficult-to-debug results. When these variables are mutated by external actors, the state of the function cannot easily be defined and therefore cannot easily be tested. Reducing the use of variables outside the scope of a function helps the programmer control the state within the function.

So, functional programming in JavaScript is:

  • Expressive
  • Stateless
  • Without side effects

This makes understanding the intent of a function’s code easier. It makes the function more composable and reusable. It makes the function easier to test because for each invocation of the function, the input has become the initial state of the function. It is simply very powerful when f(x, y) always returns g.

 

Pure Functions

With ES6 ES2015 and newer, it has become exceedingly easy to write pure functions that are expressive and easy to understand.

An example implementation of the Fibonacci algorithm with ES2015 and Node.js v6:

let fibonacci = (n, first = 0, second = 1) =>
  (n === 0)
    ? []
    : [ first, ...fibonacci(n - 1, second, first + second) ];
console.log(fibonacci(10));
[ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]

If we are not able to use Node.js v6 yet, we only lose a bit of sugar:

function fibonacci(n, first, second) {
  if (!first) first = 0;
  if (!second) second = 1;
  return (n === 0)
    ? []
    : [first].concat(fibonacci(n - 1, second, first + second));
}
console.log(fibonacci(10));
[ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]

But, let’s get rid of the array and just return the nth fibonacci number:

let fibonacci = (n, first = 0, second = 1) =>
  (n === 0)
    ? null
    : fibonacci(n - 1, second, first + second) || first;
console.log(fibonacci(10));
34

The first thing to take away from this is that the fibonacci function is effectively constant. Such that regardless of where fibonacci is called or even what called it, the function will always return the same result for a given input. Striving for this consistency and composability makes for functions that are very easy to test.

As an aside, because JavaScript is dynamically typed, input validation really ought to be done if this is an API rather than simple internal code.

Second, it is very easy to infer how the code executes. The code is clear, concise, and expressive. Unnecessary code blocks, variable definitions, and branching are reduced to their most refined forms.

Lastly, and because I have only said good things, it is not performant. Every time that array was concatenated, a brand new array was created. This is not memory nor computationally efficient. Generally, this sort of code should be used when the need for maintainability and correctness outweigh performance. Further, in JavaScript, these functions could be refactored more efficiently as necessary while maintaining the same function definitions.

 

Arrays

JavaScript comes with a very powerful class called Array. It shares similarities with arrays, but it is more akin to lists, maps, or sets. The Array class comes with a number of built-in functions like map, reduce, and filter that make functional programming much more accessible.

The map function applies a transformation against all items within an array and returns a new array of the transformed items. The transformation function need not know about the array but only the item.

let multiplyByTwo = x => x * 2;
[1, 2, 3].map(multiplyByTwo);
[2, 4, 6]

Now, when testing the transformation, rather than working with the arrays, the unit tests can work with the transformation directly. They need only validate that when given x, x multiplied with 2 is returned. Also, now the transformation can be applied elsewhere easily as it’s not wrapped up within some loop.

The filter function does exactly what you think it does. You give it a predicate and it returns you an array with only the items that match your conditions.

let lessThan10 = x => x < 10;
[1, 2, 3, 10, 20, 30].filter(lessThan10);
[1, 2, 3]

The unit tests for lessThan10 need only validate that a given item x is less than ten.

The reduce function lets you walk through the items and perform some operation that takes the result and applies the result and the operation to the next item. Traditionally, this is used for accumulation, but can be used for other purposes as well.

let incrementCountOfThe = (total, word) => total + (word.toLowerCase() === 'the' ? 1 : 0);
'The quick brown fox jumps over the lazy dog'.split(' ').reduce(incrementCountOfThe, 0);
2

The unit tests for incrementCountOfThe need only validate that the result of the given total increments by 1 or 0 from whether or not the given word to lowercase is ‘the’ now. This is less complicated than building a number of sets of words and validating that it works appropriately over each set.

 

Real World

Pure functions are great, but realistically they represent the limit to which our code should attempt to reach and not what most of our code should look like. Remember, a pure function is totally without side effects. Any IO would be a side effect. By definition, asynchronous operations would then also be a side effect. So, that would rule out all useful applications. The point, however, is that developers should reduce a function to its most expressive and composable form. A form with as little state as possible and without unreasonable side effects.

As developers, we should try to get the most out of our tools and software as efficiently as possible. Using functional programming techniques to write safer and more expressive code while also limiting side effects ultimately reduces code and testing complexity.

Experiment and have fun.


Michael Pardue, Senior Software Engineer at Accusoft and Chief T-Rex Artist. He is highly knowledgeable in a number of technologies including Node.js, microservice architecture, cloud computing, continuous integration, and unit testing. He holds a Bachelor of Science in Computer Science from University of South Florida. He ends his days playing musical instruments and video games.