Higher Order Functions: Behind the Scenes

Written by odemeulder | Published 2017/08/27
Tech Story Tags: javascript | higher-order-function | programming

TLDRvia the TL;DR App

This post is NOT a post to show you how to use forEach, map, filter and reduce, NOR is it a post showing the benefits of higher order functions. There are plenty of other articles that do that.

When I want to understand a basic concept, I like to try to implement it myself, even if it is a very basic version. People learn in different ways, this works for me. So let me take you on this journey as I try to create my own forEach, map, reduce and filter functions in JavaScript.

myForEach

In order to disambiguate from the built-in forEach, let’s call my new function myForEach.

First thing to do is to look at the call signature. So, the forEach function you know does something like this:

let arr = [ 1, 2, 3, 4 ]

// log each item of the arrayarr.forEach(console.log)

// double each item of the array and log it to the consolearr.forEach((val) => console.log(val * 2))

We’re sort of looking for a function that takes two parameters, an array and callback function. The callback function could be a function definition or an anonymous function. And what we want this to do is to iterate over each item in the array and call the callback on that item. So let’s give it a shot.

function myForEach(arr, callback) {for (let i = 0; i < arr.length; i++) {callback(arr[i])}}

let arr = [ 1, 2, 3, 4 ]

// log each item of the arraymyForEach(arr, console.log)

// double each item of the array and log it to the consolemyForEach(arr, val => console.log(val * 2))

Seems simple enough and it works. But … but … it’s not exactly the same. If you try arr.myForEach(console.log), you get TypeError: arr.myForEach is not a function. That is because we declared myForEach as a global function. And really, it should be part of the prototype of the Array object. Note the purpose of this article is not to explain how prototypical inheritance works. In order to achieve what we want, we need to do this as follows.

Array.prototype.myForEach = (callback) => {for (let i = 0; i < this.length; i++) {callback(arr[i])}}

// log each item of the arrayarr.myForEach(console.log)

When I tried running the code above, nothing happened. What is wrong?

Trying to get to fancy with arrow functions. Arrow functions don’t have their own this or arguments binding. So when I call this inside the function definition, it does not refer to the Array object itself, as I naively expected. Here is the correct way to do it. This StackOverflow post has the explanation.

Array.prototype.myForEach = function (callback) {for (let i = 0; i < arr.length; i++) {callback(arr[i])}}

// log each item of the arrayarr.myForEach(console.log)

Beautiful. Now that we have the mechanics down, we can move on to the other higher order functions.

myMap

The signature is as follows. Imagine we have an array of numbers, and want to get an array of the squares of these numbers.

let arr = [ 1, 2, 3, 4 ]

let arrNew = arr.map(val => val * val)

// orconst squareNumber = n => n * nlet arrNew2 = arr.map(squareNumber)

console.log(arrNew)// outputs [ 1, 4, 9, 16 ]

So we we apply to an array: a function that takes a callback, and returns a new array. The callback is a function that takes an input value and usually transforms that input value into something else.

Array.prototype.myMap = function(callback) {let ret = []for (var i = 0; i < this.length; i++) {let newVal = callback(this[i])ret.push(newVal)}return ret}

We iterate through the array (represented by this), apply the callback function, and push the resulting value onto a new array. And subsequently return that new array.

We could write this more succinctly using our new myForEach function.

Array.prototype.myMap = function(callback) {let ret = []this.myForEach(val => ret.push(callback(val)))return ret}

Moving on.

myFilter

By now we’re starting to get the hang of it. Here is the filter call signature.

let people = [{ 'name': 'Bob', 'age': 70 },{ 'name': 'Sue', 'age': 30 },{ 'name': 'Joe', 'age': 18 } ]

let youngPeople = people.filter( person => person.age < 69 )// returns array with two objects.// [ { 'name': 'Sue', 'age': 30 },// { 'name': 'Joe', 'age': 18 } ]

// orconst isYoung = person => person.age < 59let youngPeople = people.filter(isYoung)

So the filter expects a callback which returns a true or false, also referred to as a predicate. The function returns a new array. Based on our experience we can quickly whip this up.

Array.prototype.myFilter = function(callback) {let ret = []this.myForEach( (val) => {if (callback(val) === true) ret.push(val)}return ret}

Pretty straight forward. Note I could have written if (callback(val)) instead of if (callback(val) === true), but I did this to emphasize that we expect the callback to return true or false.

myReduce

The reduce function is a little more complex to grok. The textbook example of reduce is to sum elements of an array. This is what it would look like.

let arr = [ 1, 2, 3, 4 ]

let sum = arr.reduce( (acc, val) => acc + val )

console.log(sum)// prints out 10

The callback takes two arguments, the first one is often referred to as the accumulator, and the second one is the value of a particular item of the array. In this example, the accumulator keeps a running total, and with each iteration over the array, its value is added to the accumulator. And at the end the accumulator contains the value we are trying to get.

This is what the function looks like in its most basic form.

Array.prototype.myReduce = function (callback) {let retthis.myForEach( val => {if (ret === undefined) ret = valelse ret = callback(ret, val)}return ret}

It’s a little trickier than the other higher order functions we defined above. Note how we need to check of ret is undefined or not. This brings up another point. The JavaScript reduce function lets you specify an initial value. That’s not hard to add to our function. We just add an init parameter to the function definition and set the ret variable to init initially.

Array.prototype.myReduce = function (callback, init) {let ret = initthis.myForEach( val => {if (ret === undefined) ret = valelse ret = callback(ret, val)}return ret}

const sumTwoValues = (a, b) => a + b

let sum = arr.myReduce( sumTwoValues , 0 ) // added 0 as an argument for initial value.

console.log(sum)// prints 10

Improvements

For each of the functions above, we could make it such that we include the index of the array element in the callback signature and make it available in the callback. This should allow us to do the following for example.

var arr = [ 1, 2, 3, 4, 5, 6 ]

arr.forEach( (val, idx) => { if (idx % 2 === 0) console.log(val) }// prints the value of every other item in the array.

arr.map( (val, idx) => idx % 2 === 0 ? val * 2 : val * 3 )// create a new array where every even item has been doubled and every odd item has been trippled

arr.map( (val, idx) => idx == 0)// return an array with only the first item

The implementation is quite easy. In our myForEach function, we pass the value and the index to the callback. And since we use myForEach in all the other functions, we can easily include the index in the callback call.

Array.prototype.myForEach = function(callback) {for (var i = 0; i < this.length; i++) {callback(this[i], i)}}

Array.prototype.myMap = function(callback) {let ret = []this.myForEach((val, idx) => ret.push(callback(val, idx)))return ret}

Array.prototype.myFilter = function(callback) {let ret = []this.myForEach((val, idx) => { if (callback(val, idx)) ret.push(val)} )return ret}

Array.prototype.myReduce = function(callback, init) {let ret = initthis.myForEach( ( val, idx ) => {if (ret === undefined) ret = valelse ret = callback(ret, val, idx)})return ret}

One more thing we should do is some error checking. For instance we should make sure that the callback is a function. We should check that the array is not null.

Array.prototype.myForEach = function(callback) {if (this == null) { throw new TypeError('array is null') } if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function' }for (var i = 0; i < this.length; i++) {callback(this[i], i)}}

Array.prototype.myMap = function(callback) {if (this == null) { throw new TypeError('array is null') } if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function' } let ret = []this.myForEach((val, idx) => ret.push(callback(val, idx)))return ret}

Array.prototype.myFilter = function(callback) {if (this == null) { throw new TypeError('array is null') } if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function' } let ret = []this.myForEach((val, idx) => { if (callback(val, idx)) ret.push(val)} )return ret}

Array.prototype.myReduce = function(callback, init) {if (this == null) { throw new TypeError('array is null') } if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function' } let ret = initthis.myForEach( ( val, idx ) => {if (ret === undefined) ret = valelse ret = callback(ret, val, idx)})return ret}

That’s the end. The actual Javascript forEach, map, filter and reduce functions are a more complex. But by coding this base implementations, you may better understand how they work under the hood.

If you’ve enjoyed it, send me some love and click the heart below.


Published by HackerNoon on 2017/08/27