2017-03-05

Looper Turned Splitter, or: How I Learned to Stop Mutating and Went Functional

I stumbled upon the subject of functional programming called by its name by an accident. Kyle Simpson posted on his Twitter link to Douglas Crockford’s “Monads and Gonads” presentation to exact moment where the latter was referring to syntax highlighting as something that could be used by children but should be avoided by older programmers. I beg to differ, but that’s beside the point. The point is I didn’t stop the clip. Very soon I got interested in subject of monads and, in a broader sense, of functional programming. And it got me thinking. Writing code without mutating variables? Not using loops? Pretty rad or perhaps even revolutionary. But above all, challenging at first. I mean, again, with clarity this time, no loops? Seriously?

And thus a vision had been planted in my brain.

Long story short, after some reading I started experimenting with this paradigm, only to find it much easier to debug and to contain program’s logic. One of my teachers said once that it comes naturally to switch to better solution from worse one, but to switching back from better to worse requires some effort. And so I stayed. It simply optimized my time as a dev, so why would I drop it?

“When all you have is a hammer, everything starts to look like a monad.”

-- HipsterHacker on Twitter

I started zealously rewriting all of my loops operating on arrays to at first far cousin of functionals, forEach(), and later to map(), filter(), and reduce(). This will be the story of those first steps in a new realm of FP.

Map

This is somewhat natural beginning. Creating a new array based on existing one is often encountered in coding. Problem: imagine you want to double all the values in given array. So there goes the old school,

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var newArray = [];

for (var i = 0; i < array.length; i++) {
    newArray.push(array[i] * 2);
}

Shyly, I went with my first functional approach:

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var newArray = [];

array.forEach(function (item) {
    newArray.push(item * 2);
});

It’s still about mutating the newArray but there is no longer i variable. That means no checking of condition, that means no incrementing iterator, that means no useless var dangling after that — should I reuse it in another loop, or rather create j and then k, etc.? (Okay, with ES6’s let we wouldn’t have this situation but in the project I was working on at the time we had to use ES5, so that was a thing.) What I liked even at this stage was containing array’s transformation logic in its own scope.


It was my new solution for a moment. At some point I created Code Wars account and started solving katas there. After each I was checking other people solutions and learning more. (I can only recommend it to anyone learning a new language.) That’s where I picked up the following technique:

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const newArray = array.map(function (item) {
    return item * 2;
});

Instead of creating empty array and pushing another elements there, I was now creating the final version of transformed original array. Map() creates a new array and leaves the old one intact. Neat. Plus, some would say that we have one line of code less, though in functional approach number of lines is of no priority to me. But I’ll get back to that.

By the way, ES6 version has even less lines:

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const newArray = array.map(item => item * 2);

I still use forEach() from time to time but mostly for DOM operations, e.g. take NodeList and make some operation on them.

Filter

Let modify our original problem: imagine that you want to double only even values and get rid of rest. Let’s start with old school.

var array    = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var newArray = [];

for (var i = 0; i < array.length; i++) {
    if (array[i] % 2 == 0) {
        newArray.push(array[i] * 2);
    }
}

Works? Works.

But how to do that with .map()? Map will transform every element. New array is always 1:1 when it comes to its length. It was not long before I discovered filter() which, depending on return value being true or false, creates new array with only those elements that match our condition. Shall we? (And let’s get straight to ES6 edition this time.)

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const newArray = array
  .filter(item => item % 2 == 0)
  .map(item => item * 2);

The biggest beauty here lays in doing exactly one thing at the time. In loop based solution we get into first iteration, we check if condition is met, and then we do (or not) the final operation. And then we repeat until the main condition is finally met. In functional approach we split those operations. First we take all even values and then we perform our mapping transformation. This gets important and useful when there is more steps.

Iterations versus steps

New problem: take an array of objects {value: someNumber} and create new array with even numbers doubled but returned to {newValue: doubledNumber}. (Typical thing you’ll be dealing with in professional life as programmer)

// the array
const array = [
    { value: 1 }, 
    { value: 2 }, 
    { value: 3 }, 
    { value: 4 }, 
    { value: 5 }, 
    { value: 6 }, 
    { value: 7 }, 
    { value: 8 }, 
    { value: 9 }, 
    { value: 10 }
];

// the old way
let newArray = [];
for (let i = 0; i < array.length; i++) {
    if (array[i].value % 2 == 0) {
        newArray.push({ newValue: array[i].value * 2 });
    }
}

// the new way
const newerArray = array
    .map(object => object.value)
    .filter(value => value % 2 == 0)
    .map(value => value * 2)
    .map(doubledValue => ({ newValue: doubledValue}));

Let’s see what happens here. As previously, we need additional iterator to keep track of what we’re doing but it gets worse. array[i].value means that we read property i of array and then value of array[i]. And that’s twice in each loop. (Alternative would be storing it in… another variable.) Then we push object literal with new property that is calculated old value.

Meanwhile, in new version each step does one thing. Last step is intentionally split into two because two things happen there. Adding new step is easy. Removing obsolete step is easy. As well as debugging it by simply putting breakpoints in each step. It is more likely that error comes from wrong single step than single iteration.

Reduce

I can speak only for me and my friend (greetings, Krzysiek!), but I think every person that discovered functional programming area in JavaScript would like to use reduce() as fast as possible. The thing is it doesn’t happen that often. In the beginning I was trying really hard to find a problem even remotely resembling one that would have needed reduce(). And life kept laughing at me.


Funnily enough, I learned about reduce() before other functions, during an interview. My future team leader who never became my team leader asked me a question: “How would you sum up all values from array but without using a loop?” to which I replied that I would look up for some native method first because it sounds like that. That wasn’t good enough. (Professional term for what I’ve done, even if unintentionally, is test smartness.)

I am embarrassed to embed it here but well,

const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

var sum = array.reduce((result, value) => result + value);

I suppose it’s hard to come up with some more interesting examples. Reduce() produces a single value out of array. But who said it cannot be another array? How ‘bout this?

const array = [1, 2, 2, 3, 4, 2, 5, 6, 7, 4, 8, 9, 10];

const distinctArray = array.reduce((final, current) => final.indexOf(current) > -1 ? final : final.concat(current), []);

We take an array and return another array with all the repeated values out. All in vanilla.


But then I found a function that could really go well with reduce(). We had a function that was parsing 'templates with {values} to be replaced with, well, values' and it was using loop. And I started thinking, how would I do that without mutating string — see, I wasn’t even looking for explicit reduce() use at this point — when it struck me. In a nutshell,

// mocked stuff
const stringTemplate = 'I’m sorry, {astronautName}. I’m afraid I can’t do that. This mission is too important for me to allow you to {unwantedThing} it.';

const values = {
    astronautName: 'Dave',
    unwantedThing: 'jeopardize'
};

// original function
function parseTemplate(stringTemplate, values) {
    for (var key in values) {
        if (values.hasOwnProperty(key)) {
            stringTemplate = stringTemplate.replace(new RegExp('{' + key + '}', 'g'), values[key]);
        }
    }

    return stringTemplate;
}

// new function
// mind you, it happened in ES5 environment so no arrow functions this time
function parseTemplateFP(stringTemplate, values) {
    return Object.keys(values).reduce(function (string, key) {
        return string.replace(new RegExp('{' + key + '}', 'g'), values[key]);   
    }, stringTemplate);
}

// edition 2024
const parseTemplateFP = (stringTemplate, values) =>
    Object.entries(values).reduce(
      (string, [key, value]) => string.replace(new RegExp(`{${key}}`, 'g'), value),
      stringTemplate
    );

Reads better in my opinion.

As a bonus, my favourite use, which was done by Eric Elliott (a lost link that redirects to his main Medium page):

const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

const fn1 = s => s.toLowerCase();
const fn2 = s => s.split('').reverse().join('');
const fn3 = s => s + '!'

const newFunc = pipe(fn1, fn2, fn3);
const result = newFunc('Time'); // emit!

But still, reduce() will be the rarest in the pack. Cherish each and every one of them.

Conclusion (for now)

That was of course just a beginning. And once I switched to functional programming paradigm for good, there was no going back. From worse to better, just like my teacher said. And after a few months with this, I noticed that jQuery object can be used like that. And that observables in RxJS don’t have to have imperative handler in subscribe(). Writing goes faster and seeing what parts of code do is clearer.

You let them in and now they’re everywhere.

-- Shriekback, "Hammerheads"

To be continued?

Perhaps. There is still slice(), concat(), sort(), reverse(), and others. And other aspects of functional programming.