I keep thinking about writing more, but the more I think about it, the less I do it. So today I stopped thinking and started writing.
It can only go downhill from here…
Mostly, I couldn’t think of anything to write about. Most things I know have been written about TO DEATH. But today, I realised: that’s ok! Everyone learns things differently (which is also ok, by the way), so it may be that somebody doesn’t understand something because it just hasn’t been demonstrated, or explained, in a way that suits their learning style.
As well as that, they say the best way to cement your learning of something is to write it down, or pass it on. Maybe this will help someone, even if that someone is just me!
This morning, I was thinking about compositional chaining of functions in Javascript — something we are doing more and more of at work as we move to a more functional style (yes, yes - I know it’s not REALLY functional programming, but it’s a step in the right direction. Note, I said STYLE. We’re not going full blown Haskell and twiddly moustaches just yet…).
Anyway, I thought I would put down my thoughts on the old favourites - map, filter and reduce. These took some brain-wrangling to get my head around. I read a lot, watched a lot of videos (thanks Professor Frisby), but it just wouldn’t stick. They aren’t difficult, but my brain just couldn’t deal with it, for whatever reason.
So, first up is Map :
Map can be called on an array, which then calls the function passed in to it on each item in the first array.
const addOne = num => num + 1;
let nums = [1,2,3,4,5,6,7,8,9];
let jsMap = nums.map(addOne); // [2, 3, 4, 5, 6, 7, 8, 9, 10]
You can paste this into your editor, or IDE and run with Node to see this for yourself (same with all of these examples).
It’s also useful to remember that this doesn’t have to be a function passed in (although, this will probably prove more beneficial in filter and reduce):
let nums = [1,2,3,4,5,6,7,8,9];
let jsMap = nums.map(num => num>= 5);
// [false, false, false, false, true, true, true, true, true]
The important thing about map is that the original array is unmodified:
const addOne = num => num + 1;
let nums = [1,2,3,4,5,6,7,8,9];
let jsMap = nums.map(addOne); // [2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(nums); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
It seems to me that this is on of the main bonus points of Functional Programming - immutability. Basically, this means that the original thing remains unchanged. Not mutated. By radiation or any other thing.
So, back to my opening gambit- what is different about this explanation? I’m sure you’ve seen the addOne function written a trillion times if you’re trying to get your head around these “pure functions. Well, nothing. So far.
But now, I thought I would write a manual map function. Using good, old-fashioned JavaScript for loops (although, these are now pretty much the Devil). Everyone understands a forloop. If you don’t, I would go back to something more basic….
So, here goes (using our addOne function from earlier):
const map = (arr, func) => {
let results = [];
for(let i = 0; i < arr.length; i++) {
let result = func(arr[i]);
results.push(result);
};
// Note that results is returned -
// the original array that was passed in is non-radioactive
return results;
}
let ourOwnVerySpecialMapResults = map(nums, addOne);
// [2, 3, 4, 5, 6, 7, 8, 9, 10]
// Nums is still unchanged
console.log(nums); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
A couple of things to note - the original nums array hasn’t become radioactive (or changed in any way). Where the addOne function is passed in to our map, only the name is passedwithout the function being called - that is done in the manual map we wrote.
But the main thing to take away from this is that it is just a for loop!!
Every result of the array value being passed to the function is pushed to a new array that is returned.
I guess the next question is: if it is this easy, why do we use the JS map? Well, we can easily chain other functions on to the end of our mapped result. More on that later. Part 2, maybe (if there is one)…
Next, Filter:
As with map, filter is really just another for loop in disguise. It walks through an array, checks each value and passes it to a new array if the value matches expectations. Like this:
let nums = [1,2,3,4,5,6,7,8,9];
let jsFilter = nums.filter(num => num > 5); // [6, 7, 8, 9]
So, it walks through nums, checks each value to see if it is greater than five:
num => num > 5
And returns it in the array if it is. Simples.
There we have it, a simple JS filter. As before, let’s write our own:
const moreThanFive = num => {
if (num > 5) {
return num;
}
}
const filter = (arr, check) => {
let results = [];
for(let i = 0; i < arr.length; i++) {
if (check(arr[i])) {
results.push(arr[i]);
}
};
return results; // nums is still non-mutant
};
let ourOwnVerySpecialFilterResults = filter(nums, moreThanFive);
// [6, 7, 8, 9]
As before, it is just a for loop, but with an if statement inside that controls what is pushed to the results array.
Is this making sense? Are you with me? I hope so…
Finally, Reduce:
So. Reduce takes two things: a function and a ‘3.5 inch floppy disk’ (or storage device of your choice). It walks through the array it is called on, passes each value to the function and then saves it to the floppy disk (Minions USB memory stick). The floppy disk doesn’t have to be a number, it can be an array etc. - just remember that it will stick to it’s type: if your floppy disk (64gb Sandisk micro SD card) is an array to begin with, the results of the function will be pushed to that array. If it is a number you can dictate what to do - add, subtract etc.
let jsReduce = nums.reduce((sum, num) => {
if (moreThanFive(num)) {
return sum + num;
} else {
return sum; // nums is unaffected still
}
// The floppy disk here is the (number) 0 passed after the function:
}, 0);
// 30 (6 + 7 + 8 + 9)
This will go through our nums array, check if the value is moreThanFive. If it is, it will save it to the floppy disk (spiral bound, lined notebook).
You can think of it as a kind of map function, but all of the results are smooshed down into one.
This is probably the more complicated of the three. Take some time to make sure you get what it has done, not necessarily how it has done it.
So, let’s try writing our own reduce function:
const reduce = (floppyDisk, arr) => {
for(let i = 0; i < arr.length; i++) {
if (arr[i] > 5) {
floppyDisk += arr[i];
}
};
return floppyDisk; // nums is still not a mutant/zombie/whatever
}
This is a very simple version - it will only work with numbers, but you get the idea. The JS version is much more powerful - it can push to arrays etc.
BUT, you can see it is still just a forloop with an if check inside it.
I hope this has demistified these three higher-order functions. I think I was fairly confident in using them before now, but writing this has really cleared it up for me.
Give them a try, see how it works out for you.
If it helps, let me know - leave a comment, retweet or whatever.
If it’s wrong, let me know.
There may be a better way to do it than this, but I’ve tried to keep it clear and simple.
REMEMBER KIDS: Reducing variables down to one letter REALLY doesn’t help anybody when they are trying to learn.
Top comments (0)