Making Your JavaScript More Functional

Functional Programming in JavaScript is all the rage right now, with new libraries, languages, and transpilers popping up everywhere. You might be thinking, “How do I make this radical paradigm shift?”

The answer is, you don’t have to. Not right away anyhow (or ever), and not completely. A Functional JavaScript programming style can have it’s advantages but you don’t need to go all in to benefit from some of the main tenets of its functional methodologies.

Simply by reducing your cyclomatic complexity and doing so with functions that have fewer side effects, you can make your code easier to read and maintain. What you are left with is something that smells a lot like functional programming.

Less Complex, Fewer Side Effects

Let’s say we have the following mock data:

// mock data, perhaps the result of an AJAX call
var collection = [
    {value: 1, display: 'one', options: [
        {value: 1.1, display: 'one.one'},
        {value: 1.2, display: 'one.two'},
        {value: 1.3, display: 'one.three'}
    ]},
    {value: 2, display: 'two', options: [
        {value: 2.1, display: 'two.one'},
        {value: 2.2, display: 'two.two'},
        {value: 2.3, display: 'two.three'}
    ]},
    {value: 3, display: 'three'},
];

And we want to serialize that data for use in something else, so we need it to look like this:

var serialized = [
    {id: 1, text: 'one', items: [
        {id: 1.1, text: 'one.one'},
        {id: 1.2, text: 'one.two'},
        {id: 1.3, text: 'one.three'}
    ]},
    {id: 2, text: 'two', items: [
        {id: 2.1, text: 'two.one'},
        {id: 2.2, text: 'two.two'},
        {id: 2.3, text: 'two.three'}
    ]},
    {id: 3, text: 'three'},
];

We could simply do the following:

var collection = ... // see example above
var serialized = [];
for (var i = 0; i < collection.length; ++i) {
    if (!serialized[i]) {
        serialized[i] = {};
    }
    serialized[i].id = collection[i].value;
    serialized[i].text = collection[i].display;

    if (collection[i].options) {
        serialized[i].items = [];
        for (var j = 0; j < collection[i].options.length; ++j) {
            if (!serialized[i].items[j]) {
                serialized[i].items[j] = {};
            }
            serialized[i].items[j].id = collection[i].options[j].value;
            serialized[i].items[j].text = collection[i].options[j].display;
        }
    }
}

But on a scale of “I can’t read this” to “OMG this is brilliant!”, we are far closer to the former. There are a number of things wrong with the code above.

  • It’s not reusable
  • It directly mutates serialized
  • It has to know what state collection is in
  • It takes two, nested loops or O(n^2) to process

We can fix each one of these problems in turn.

Step 1: Making it Reusable

By simply wrapping our loops in a function we’ve made that code more reusable. Not much more though, since we can only change serialized as it pertains to collection and only in the state collection is in now. But this is only the first step.

var collection = ... // see example above
var serialized = [];
serializer();
function serializer() {
    for (var i = 0; i < collection.length; ++i) {
        if (!serialized[i]) {
            serialized[i] = {};
        }
        serialized[i].id = collection[i].value;
        serialized[i].text = collection[i].display;

        if (collection[i].options) {
            serialized[i].items = [];
            for (var j = 0; j < collection[i].options.length; ++j) {
                if (!serialized[i].items[j]) {
                    serialized[i].items[j] = {};
                }
                serialized[i].items[j].id = collection[i].options[j].value;
                serialized[i].items[j].text = collection[i].options[j].display;
            }
        }
    }
}

Step 2: Don’t Mutate

If we move our array instantiation inside our function and return a new array, we no longer mutate our serialized array. Instead, serialized becomes the product of our function.

var collection = ... // see example above
var serialized = serializer();
function serializer() {
    var newArray = [];
    for (var i = 0; i < collection.length; ++i) {
        if (!newArray[i]) {
            newArray[i] = {};
        }
        newArray[i].id = collection[i].value;
        newArray[i].text = collection[i].display;

        if (collection[i].options) {
            newArray[i].items = [];
            for (var j = 0; j < collection[i].options.length; ++j) {
                if (!newArray[i].items[j]) {
                    newArray[i].items[j] = {};
                }
                newArray[i].items[j].id = collection[i].options[j].value;
                newArray[i].items[j].text = collection[i].options[j].display;
            }
        }
    }
    return newArray;
}

Step 3: Be Stateless

Now if we pass a schema as an argument into serializer, we no longer need to know anything about our collection in the body of the function. As a result, we can start to use this function to serialize other objects as well.

var collection = ... // see example above
var serialized = serializer(collection, {
    id: 'value',
    text: 'display',
    items: 'options'
});
function serializer(data, schema) {
    var newArray = [];
    for (var i = 0; i < data.length; ++i) {
        if (!newArray[i]) {
            newArray[i] = {};
        }
        newArray[i].id = data[i][schema.id];
        newArray[i].text = data[i][scheme.text];

        if (data[i][schema.items]) {
            newArray[i].items = [];
            for (var j = 0; j < data[i][schema.items].length; ++j) {
                if (!newArray[i].items[j]) {
                    newArray[i].items[j] = {};
                }
                newArray[i].items[j].id = data[i][schema.items][j][schema.id];
                newArray[i].items[j].text = data[i][schema.items][j][schema.text];
            }
        }
    }
    return newArray;
}

Step 4: No loops, Be Recursive

Once you’ve gone through making code reusable, limiting mutation, and limiting awareness of state, you begin to have a function that is so reusable, you can use it within itself. To do that effectively and in a way that is readable and maintainable, you can replace loops with more functional alternatives, followed by recursion.

In the case below, I’ve swapped the top level for loop for Array.prototype.map and the inner for loop with a recursive call to the serializer function.

var collection = ... // see example above
var serialized = serializer(collection, {
    id: 'value',
    text: 'display',
    items: 'options'
});
function serializer(data, schema) {
    return data.map(_mapHandler);
    function _mapHandler(branch) {
        return {
            id: branch[schema.id],
            text: branch[schema.text],
            items: branch[schema.items] ? serializer(branch[schema.items], schema) : false
        }
    }
}

Summary

This is a simplified solution with no safety checks or error handling, but it illustrates that in 4 simple steps, we reworked our code from “I can’t read this” to “OMG this is brilliant!”. We’ve managed to make it reusable, have no side effects, be stateless and remove any fragile loops. With little pain or effort, we’ve gone from a verbose, imperative coding style, to a succinct, readable, more functional paradigm.

 

Adam Merrifield

 

2 thoughts on “Making Your JavaScript More Functional

Leave a Reply