Real Examples of Javascript Functional Programming for Beginners

lsmod
11 min readJan 5, 2020

You will find below practical examples of the most used function to know when in applying functional programming in Javascript (map, filter, sort, reverse, …).

Disclaimer:
This story was written during dark times. It may be a bit sarcastic but my heart was in the right place, I just wanted to write an introduction to functional programming with concrete examples.

Let’s put some context

Let’s say I’m a work and today Angela come to ask me something.
She “works” at marketing and asks me to give her a list of our users with email and names, so they can write “customized messages” to our clients.

So I got a list of users I fetched with an API call. It looks like this:

const users = [
{
id: 4545,
nickname: "franky",
name: "Frank",
email: "frank4545@aol.com",
country: "US",
subscriber: true
},
{
id: 4546,
nickname: "castortroy",
name: "Troy",
email: "realtroy@gg.com",
country: "US",
subscriber: false
},
{
id: 4547,
nickname: "melly99",
name: "melody",
email: "darkistheday@cusual.io",
country: "IT",
subscriber: false
}
];

I can make a get request on /users and I will receive a list of all our users (the list above).

But this API is dumb and, it sends all users with full details or nothing. I don’t need, nickname, country or to know if the user is a subscriber or not (besides I don’t really trust Angela she shouldn’t get users personals details).

Introduction to the Map function

So without knowing any functional style programming, here it’s what I would do :

const users_without_extra_info = [];
for (user of users) {
users_without_extra_info.push({
name: user.name,
email: user.email
});
}

users_without_extra_info variable would contain the following:

[
{ name: "Frank", email: "frank4545@aol.com" },
{ name: "melody", email: "darkistheday@cusual.io" },
{ name: "Troy", email: "realtroy@gg.com" }
];

Just what we needed !

Now if we want to do it in a functional manner we could use map.
That map function allows you to MAP one form of data into another (exactly what I was doing with the for loop). map can be called on an array and we perform on each element of it.
In some way map function could have been named: mapEach(). map in the end returns a new array containing all mapped/transformed objects.

const users_with_map = users.map(user => {
return {
name: user.name,
email: user.email
};
});
console.log(users_with_map);

This also prints:

[
{ name: "Frank", email: "frank4545@aol.com" },
{ name: "melody", email: "darkistheday@cusual.io" },
{ name: "Troy", email: "realtroy@gg.com" }
];

Using map our code doesn't look really different from the for loop. What's is kind of strange to get at first look is that map takes a function has an argument.
A function supposed to return the mapped object. I could have written the following instead:

function userWitoutExtratInfo(user) {
return {
name: user.name,
email: user.email
};
}
users.map(userWitoutExtratInfo); // no suprise it also returns
// [ { name: 'Frank', email: 'frank4545@aol.com' },
// { name: 'melody', email: 'darkistheday@cusual.io' },
// { name: 'Troy', email: 'realtroy@gg.com' } ]

In the end map could have been named: constructNewArrayWithMappingFunction(mappingFunction)

Well, I replaced a for loop with map function. What’s the big deal about it ?

const users_for_angela_with_map = users.map(user => {
return {
name: user.name,
email: user.email
};
});
// VS
const users_for_angela = []; // not needed with map() (it return a new array)
for (user of users) {
// With map() I don't need to botter choosing how to loop over it, and I can't make mistakes
users_for_angela.push({
// here it's pretty much the same but I can't make the mistake of modifying my original array
name: user.name,
email: user.email
});
}

So I give the list to Angela. She is please with it and give me a fake smile (her working habit).

How to Filter using ‘filter’ function

The next day when I arrive late at work (my working habit) and she’s already at my desk.
I put down my backpack and turn on my computer while she explained to me that:

Angela: THERE IS A BIG PROBLEM WITH THE LIST YOU GAVE ME. ALL THE EMAIL I SENT TO AOL EMAIL CAME BACK TO ME… YOU MUST HAVE GIVEN ME WRONG EMAIL.
Me: Yeah AOL close years ago. I will give you the list purged of AOL’s address give me 30 minutes it’s coffee time
Angela: YOU ARE GOING ON BREAK ? BUT YOU JUST ARRIVED
Me: Exactly I just arrived. I need some coffee
(see gave me that fake smile again — I replied with my poker face of the casual days)

So a half coffee later, I got back to my for loop knowing exactly how to filter those AOL address:

const users_no_aol = [];
for (user of users) {
if (!user.email.includes("aol.com")) {
// RIP AOL
users_no_aol.push({
name: user.name,
email: user.email
});
}
}
console.log(users_no_aol); // it prints:
// [ { name: 'melody', email: 'darkistheday@cusual.io' },
// { name: 'Troy', email: 'realtroy@gg.com' } ]

As it only took me 30sec (plus a half coffee time)…
So I got time to try it differently before giving the new list to Angela.

Let’s try:

const all_users = users.map(user => {
return {
name: user.name,
email: user.email
};
});

That gave me back the whole list. How could I filter in a functional manner ? With filter() of course!
Let's filter out of that list users with an AOL email.

const users_with_aol_filtered = all_users.filter(user => {
return !user.email.includes("aol.com");
});
console.log(users_with_aol_filtered); // and it prints:
// [ { name: 'melody', email: 'darkistheday@cusual.io' },
// { name: 'Troy', email: 'realtroy@gg.com' } ]

Just like map, filter returns an array, and take a function as argument. This function will check if each element needs to be kept filter could have been named contrustNewArrayKeepingOnlyCheckingTestFunction(testFunction). The testFunction provided to filter() as param perform a test and return true if the element is to be kept.

Now the benefit of filter VS for loop:

const all_users_for_angela = users.map(user => {
return {
name: user.name,
email: user.email
};
});
const users_aol_filtered = all_users_for_angela.filter(user => {
return !user.email.includes("aol.com");
});
// VS
const users_no_aols = [];
for (user of users) {
if (!user.email.includes("aol.com")) {
// it take only this line to filter with our for loop
users_no_aols.push({
name: user.name,
email: user.email
});
}
}

What did I win here…
Yes, I can’t modify my original array by mistake. I also can’t mess iteration over users. No obvious win, it also feels like my code is not so easy to read.
I can do this:

users
.map(user => {
return {
name: user.name,
email: user.email
};
})
.filter(user => {
return !user.email.includes("aol.com");
}); // and filter returns:
// [ { name: 'melody', email: 'darkistheday@cusual.io' },
// { name: 'Troy', email: 'realtroy@gg.com' } ]

Even better (just a little bit better):

users
.map(user => {
return { name: user.name, email: user.email }; // sometime one line code is better
})
.filter(user => {
return !user.email.includes("aol.com");
}); // same return returned by filter
// [ { name: 'melody', email: 'darkistheday@cusual.io' },
// { name: 'Troy', email: 'realtroy@gg.com' } ]

Now we start to see the benefit: chaining capability. map, filter, sort, slice, reverse all return a new array that you can iterate on it with any of the previous functional functions. That's ideal to prepare, filter and sort or data with a maintainable code.

Sort function a concrete example

Let’s say I bring that list back to Angela.
Then she would probably ask me something more. Like Alphabetical ordering. If you had seen her desk (where her pencil are color ordered) you will know she will definitely ask for it.

users
.map(user => {
return { name: user.name, email: user.email };
})
.filter(user => {
return !user.email.includes("aol.com");
})
.sort((userA, userB) => {
if (userA.name.toLowerCase() < userB.name.toLowerCase())
return -1;
if (userA.name.toLowerCase() > userB.name.toLowerCase())
return 1;
return 0;
});
// sort returns :
// [ { name: 'melody', email: 'darkistheday@cusual.io' },
// { name: 'Troy', email: 'realtroy@gg.com' } ]

sort takes a sorting function accepting two arguments. Those two elements are the elements to compare.
The sorting function is supposed to compare elements and return -1 if element A should be placed before element B.
On the contrary, it returns 1 if element A should be placed after element B
And finally returns 0 if compared elements are alike (same order) for example: "Hello" & "hello".
As map and filter, sort return a new array.

So 1h later:

Me: There you go. Here is the list.
Angela: Mhmm let me see… I would like to start from the bottom of the list and remove user one by one. But I can’t because it’s in the wrong order.
Me:
Me: No problem just give me 1 hour :-)

users
.map(user => {
return { name: user.name, email: user.email };
})
.filter(user => {
return !user.email.includes("aol.com");
})
.sort((userA, userB) => {
if (userA.name.toLowerCase() < userB.name.toLowerCase())
return -1;
if (userA.name.toLowerCase() > userB.name.toLowerCase())
return 1;
return 0;
})
.reverse(); // yep reverse ! No need to modify any thing to return:
// [ { name: 'Troy', email: 'realtroy@gg.com' },
// { name: 'melody', email: 'darkistheday@cusual.io' } ]

Angela: But also need the user nickname dummy !

users
.map(user => {
return { name: user.name, email: user.email, nickname: user.nickname }; // here goes your dummy nickname
})
.filter(user => {
return !user.email.includes("aol.com");
})
.sort((userA, userB) => {
if (userA.name.toLowerCase() < userB.name.toLowerCase())
return -1;
if (userA.name.toLowerCase() > userB.name.toLowerCase())
return 1;
return 0;
})
.reverse(); // returns
// [ { name: 'Troy',
// email: 'realtroy@gg.com',
// nickname: 'castortroy' },
// { name: 'melody',
// email: 'darkistheday@cusual.io',
// nickname: 'melly99' } ]

Angela: Price just told me that AOL hasn’t closed. Is just that today they have server issues so I need them back
Me: If you say so…

users
.map(user => {
return { name: user.name, email: user.email, nickname: user.nickname };
}) // no more filter (isn't it nice to delete consecutive lines and get a new feature ?!)
.sort((userA, userB) => {
if (userA.name.toLowerCase() < userB.name.toLowerCase())
return -1;
if (userA.name.toLowerCase() > userB.name.toLowerCase())
return 1;
return 0;
})
.reverse();

Angela: Thank you very much! Is it possible to get the name in big letters and the nickname with little letter ?
Me: You mean in UPPERCASE and in lowercase ?
Angela: Yes! So Is it possible ?
Me: Nothing is impossible except to work in this company for free.

users
.map(user => {
return { name: user.name, email: user.email, nickname: user.nickname };
})
.sort((userA, userB) => {
if (userA.name.toLowerCase() < userB.name.toLowerCase())
return -1;
if (userA.name.toLowerCase() > userB.name.toLowerCase())
return 1;
return 0;
})
.reverse()
.map(user => {
// could have been this above In the first map, but Angela could change one more time
return {
...user,
name: user.name.toUpperCase(),
nickname: user.nickname.toLowerCase()
};
})
; // returns:
// [ { name: 'TROY',
// email: 'realtroy@gg.com',
// nickname: 'castortroy' },
// { name: 'MELODY',
// email: 'darkistheday@cusual.io',
// nickname: 'melly99' },
// { name: 'FRANK', email: 'frank4545@aol.com', nickname: 'franky' } ]

Angela: There is a big problem! Hank just told me I need a CSV file!
Me: CSV you sure he didn’t say JSON file ?
Angela: CSV I’m sure I wrote it down on this pink post-it
Me: Isn’t purple ?

let user_csv = users
.map(user => {
return {
name: user.name.toUpperCase(), // I bet she won't care about this anymore (It's almost the end of the day afterall)
email: user.email,
nickname: user.nickname.toLowerCase()
};
})
.sort((userA, userB) => {
if (userA.name.toLowerCase() < userB.name.toLowerCase())
return -1;
if (userA.name.toLowerCase() > userB.name.toLowerCase())
return 1;
return 0;
})
.reverse()
.map(user => Object.values(user).join(";")); // join each value of the user object with ";"
console.log(user_csv); // prints:
// [ 'TROY;realtroy@gg.com;castortroy',
// 'MELODY;darkistheday@cusual.io;melly99',
// 'FRANK;frank4545@aol.com;franky' ]

Me: Whoops I forgot to put a semi column at the end of each line!
Angela: Hurry up!! it’s almost closing time and I still haven’t sent my newsletter today…

users
.map(user => {
return {
name: user.name.toUpperCase(),
email: user.email,
nickname: user.nickname.toLowerCase()
};
})
.sort((userA, userB) => {
if (userA.name.toLowerCase() < userB.name.toLowerCase())
return -1;
if (userA.name.toLowerCase() > userB.name.toLowerCase())
return 1;
return 0;
})
.reverse()
.map(user => Object.values(user).join(";"))
.map(csv_line => csv_line + ";"); // I could have done it one line before. But I like this way. One operation by map
// our last map returns:
// [ 'TROY;realtroy@gg.com;castortroy;',
// 'MELODY;darkistheday@cusual.io;melly99;',
// 'FRANK;frank4545@aol.com;franky;' ]

I still have an issue.
I get back an array of CSV line. But what I really want is the whole string (with new line at each CSV line of my array)
Without functional style I would have done it like this:

let whole_csv_string = "";
for (const line of toto) {
whole_csv_string = whole_csv_string + "\n" + line;
}
console.log(whole_csv_string); // prints:
// TROY;realtroy@gg.com;castortroy;
// MELODY;darkistheday@cusual.io;melly99;
// FRANK;frank4545@aol.com;franky;

In functional style reduce can help me with that:

proper_csv = my_list.reduce((whole_csv, csv_line) => whole_csv + "\n" + csv_line);
console.log(proper_csv); // prints:
// TROY;realtroy@gg.com;castortroy;
// MELODY;darkistheday@cusual.io;melly99;
// FRANK;frank4545@aol.com;franky;

reduce could have been named: accumulate
And guess what ? Reduce take a function as an argument.
That function received two parameters: an accumulator and the current element
So for each element of my array, the reducing function got called and receive the current element add the accumulator A really explicit name would have been:
accumulateWithAccumulatorFunction(AccumulatorFunction(accumulate, currentElement))
Well is not named accumulator but reduce ? Why? Because you call reduce on an array and it returns a single variable (here a string). So it reduce your array in some way.
This is counter-intuitive, I know.

Me: Mission accomplish !
Angela: Stop bragging. Give me the list.

A bit of functional programming theory :

I didn’t give any theoretical background explanations about functional programming.
That was on purpose. Functional programming is really mathematical oriented (as my understanding goes). In my opinion, it is too abstract to grasp quickly. That’s why I wrote this article.

Anyway, if there where only one explanation to give about functional programming is that it relies on pure function. Pure functions are functions that don’t depend on the circumstances/context/environment.
Meaning that with a given input/parameter, you’re always sure to get the same return/output.

Let say that isItTimeToQuitYourJob(age, personnalFund, motivationLevel) imaginary function is not pure. Why ? Because it won't recommend to quit your job at the end of the year (bonus time). And how does it know it the and of the year or not ? By calling an other function that returns the current date.

So when you call isItTimeToQuitYourJob like this: isItTimeToQuitYourJob(99, 100000, -1) on 10th December it return false. But if it's the first of January it would return true. It's not a pure function. Yes, it's predictable because I can check the time before calling the function... But it isn't pure ! It's also not easy to unit test, by the way...

map is pure, reverse too, as well as filter and sort. The Same array always returns the same result. Pure function.

One other important aspect is that it doesn’t modify data, it creates new one. Remember that map returns a new array ? reverse too. Same for filter and sort. It's no coincidence, it comes from functional style. It has the big advantage of enabling chaining, and to avoid side effects.

Summary:

map -> map each element into a new format.
Could have been named: mapEach
explicit imaginary name: contrustNewArrayWithMappingFunction(mappingFunction)

filter -> filter each element to keep only what's require
Could have been named: filterEach()
explicit imaginary name: contrustNewArrayKeepingOnlyCheckingTestFunction(testFunction)

sort -> sort an array using a comparing function
explicit imaginary name: contrustNewArraySortingElementWithCompareFunction(compareFunction)

reverse -> reverse array elements order
explicit imaginary name: constructNewArrayWithReversedOrder()

reduce -> Reduce an array to a single value, accumulate earh element to produce the sum of it
explicit imaginary name: accumulateWithCumulFunction(cummulFunction(cummul, currentElement))

Conclusion:

I hope it helps you see the big picture. Personally, I need concrete examples to memorize new concepts. I try to keep you entertained while reading this article (so your brain doesn’t pass out). Give it some clap if you like this article format.

And remember, to map elements, sort, filter of your array and even to reduce it, pure is the functional way !

--

--

lsmod

Self-taught programmer; passionate about Rust & Blockchains