Functional Programming in JavaScript

…the main difference between writing JavaScript code like the average Joe (or Jill) and writing it like a JavaScript ninja is understanding JavaScript as a functional language. — John Resig, Secrets of the JavaScript Ninja

Learning the fundamentals of functional programming has many benefits — the code is more succinct and readable. It’s also adds an element of fun while thinking through a problem with the lens of “how can I implement this with higher-order functions”?

Below are cherry-picked explanations and examples from around the web that best describe how to implement higher-order functions. The reason I wrote this post is because having these examples is helpful to have in one place, and perhaps you will find it helpful as well. The explanations in this post are gleaned from the following sources:

Higher order functions

Generally speaking, higher-order functions are functions that accept other functions as arguments. Functions that can be passed as an argument is called a first class function. The first class function executes on each item in the array, not on the array as a whole.

Being able to write what we want to do instead of how we do it is the aim of using higher-order functions. The lower-abstracted details are performed behind the scenes.

forEach

array.forEach(callback[, thisArg])

forEach simply iterates over an array and invokes a function on each item in the array:

var nums = [1, 2, 3];
nums.forEach(function(num) {
  console.log(num * 2)  // 2, 4, 6
});

Note: using a return for the inner function does not work, due to how forEach is setup. forEach simply applies an action to an array item without returning the result.

Also important: the function context for forEach is global if no thisObject is passed. So:

var nums = [1, 2, 3];
nums.forEach(function(item) {
  console.log(this);  // Returns global window x 3
});

Passing a function context:

var nums = [1, 2, 3];
nums.forEach(function(item) {
  console.log(this);  // Returns `[1, 2, 3]` x 3
}, nums);

map

arr.map(callback[, thisArg])

map applies a function to each item in an array and returns a resulting array. This is just like forEach, but instead of discarding the values returned by the function, it builds a new array of the transformed values.

var nums = [2, 4, 6, 8];

var squareIt = function(num) {
  return num * num;
};

nums.map(squareIt);  // Returns `[4, 16, 36, 64]`

Another map example:

var words = ['foot', 'goose', 'moose', 'kangaroo'];

function fuzzyPlural(single) {
  var result = single.replace(/o/g, 'e');
  if (single === 'kangaroo') {
    result += 'se';
  }
  return result;
}

words.map(fuzzyPlural);  // Returns `['feet', 'geese', 'meese', 'kangareese']`

Or we can use it to return values without transforming them:

var sandwiches = [
  {name: "hamburger", "price": "$6"},
  {name: "BLT",   "price": "$4"},
  {name: "egg salad",  "price": "$4"}];

var prices = sandwiches.map(function(sandwich) { return sandwich.price; });

filter

arr.filter(callback[, thisObject])

Sometimes we only want to get entries that satisfy a certain condition.

Similar to map, filter takes in an array of values and returns a new array. The difference is filter invokes a function that returns a boolean result. If the function returns true, the value is added to the new array. Values that return a falsey result are filtered out.

var nums = [1, 2, 3, 4, 5, 6];

var isEven = function(num) {
  return num % 2 == 0;
}

nums.filter(isEven);  // Returns `[2, 4, 6]`

Another example:

var people = [
  {name:"John",  age: 40},
  {name:"Sue",   age: 30},
  {name:"Mary",  age: 55},
  {name:"Jack",  age: 20}];

people.filter(function(person) {
  return person.age == 40 || person.age == 30;
});

// returns `[John, Sue]` (an array of objects)

some

arr.some(callback[, thisObject])

Often we want to know whether some or all items in an array satisfy a specific condition.

some returns a boolean for the array. It returns true if some of the items satisfy the condition. The condition is determined by the first-class function, which should return a boolean result.

var nums = [5, 8, 2, 10];
var current = 7;

function higherThanCurrent(num) {
  return num > current;
}

if ( nums.some(higherThanCurrent) ) {
  console.log('Some nums are higher than current')
}

every

arr.every(callback[, thisObject])

We can also test if all values satisfy a specific condition with the every method.

var nums = [5, 8, 2, 10];
var current = 7;

function higherThanCurrent(num) {
  return num > current;
}

if ( nums.every(higherThanCurrent) ) {
  console.log('All nums are higher than current.');
} else {
  console.log('Some nums are lower than current.');
}

indexOf and lastIndexOf

arr.indexOf(searchElement[, fromIndex])

arr.lastindexOf(searchElement[, fromIndex])

indexOf is used to test whether an element is present in an array, which searches with a strict === operator.

The method returns an integer index of the searched element in the array. An element not found returns -1. indexOf also takes an optional fromIndex parameter to start a search from a specific position.

var data = ['two', 'four', 'five', 'six'];

data.indexOf('four');  // 1
// Start search from index position 1, return index position for 'five'
data.indexOf('five', 1);  // 2
data.indexOf('eight');  // -1

lastIndexOf starts a search from the end of an array. It takes the same parameters as indexOf, including the optional fromIndex parameter.

var data = ['two', 'four', 'five', 'six', 'four'];

data.lastIndexOf('five');  // 2
data.lastIndexOf('four', 3);  // 1

reduce and reduceRight

arr.reduce(callback,[initialValue])

arr.reduceRight(callback,[initialValue])

reduce and reduceRight allow us to flatten an array into a single value. The intialValue parameter is optional — if omitted it defaults to the first element of the array. Here’s a simple example of reduce:

var nums = [1, 2, 3, 4];

var sum = nums.reduce(function (previous, current) {
  return previous + current;
});

console.log(sum);  // Returns 10

What’s happening here? We are going through the array and getting:

  1. The previous value of every callback, which initially is the first element since it’s equal to intialValue
  2. The current value of every callback, which at first call is 2
  3. The last two arguments — index and array

Finally we return the sum of our previous and current values, which becomes the previous value of the next iteration, and the current value is set to the next element. It looks like:

// initial set
previous = initialValue = 1, current = 2

// first iteration
previous = (1 + 2) =  3, current = 3

// second iteration
previous = (3 + 3) =  6, current = 4

// third iteration
previous = (6 + 4) =  10, current = undefined (exit)

Generic nature of higher-order functions

What’s really cool about these higher-level functions is they can be applied to other object types as long they have a length property and numeric indices.

// Get the reference to the map method
var map = Array.prototype.map;

// Call it on a string, which will return an array
var helloArray = map.call('hello world', function (char) {
  return char + '*';
});

// Join the array items
console.log( hello.join('') ); // 'h*e*l*l*o* *w*o*r*l*d*'

We can also do the opposite. Such as applying a string method to an array:

var toUpperCase = String.prototype.toUpperCase;

var upper = toUpperCase.apply(['foo', 'bar']).split(',');

console.log(upper);  // ['FOO', 'BAR']

Notice how call and apply are used in these examples. This “generic nature” is the result of having the ability to pass in a different this to the native object methods, via call or apply. call is used when there is a set number of parameters to pass. apply is used to pass in an array.

Comments