Front End Engineering, May 2015

Functional Programming (FP) in JavaScript

language features that aid FP:

  • immutable data
  • first class functions
  • tail call optimization

techniques used to write functional code:

  • mapping
  • reducing
  • pipelining
  • recursing
  • currying
  • use of higher-order functions

advantageous properties of functional programs:

  • parallelization
  • lazy evaluation
  • determinism

What is FP?

The Absence of Side Effects™.

Functional Functions (FF) don't rely on data outside the function, and doesn't mutate data that exists outside the current function. (immutable data)

A Functional Function (FF)

Unfunctional Function (UF):

var a = 0
function increment1(){
    a++
}

Functional Function (FF):

function increment(a){
    return a+1 // returns a new copy incremented by 1
}

Iterating in FP

Classic UF way to loop over items in an array:

var names = ['Matt', 'Jesse', 'Brian']

// classic for loop
for(var i = 0; i < names.length; i++){
    console.log(names[i])
}

Slightly more FF way:

var names = ['Matt', 'Jesse', 'Brian']

// classic for loop
names.forEach(function(v, i){
    console.log(v)
})

Mapping

Map takes a function and a collection of items.

It makes a new collection, and passes each item in the old collection to the function, and inserts the return value into the new collection.

Example 1, "map words to lengths":

function length(v){
    return v.length
}

['Matt', 'Jesse', 'Brian'].map(length)
// --> [4, 5, 5]

Example 2, "square every number":

function sq(v){
    return v*v
}

[1, 2, 3, 4, 5].map(sq)
// --> [1, 4, 9, 16, 25]

In languages that support higher order functions, like JavaScript, we can use anonymous functions:

[1, 2, 3, 4, 5].map(function(v){
    return v*v
})
// --> [1, 4, 9, 16, 25]

In ES6 this can be even more terse:

[1, 2, 3, 4, 5].map((v) => v*v)
// --> [1, 4, 9, 16, 25]

Mapping, More FP

The following examples will use rand(a,b), which returns a random integer between a and b. Here's the code for rand():

function rand(a,b){
    var diff = b-a+1
    return Math.floor(Math.random()*diff) + a
}

Classic UF way to loop over and replace items in an array with random code names:

var names = ['Matt', 'Jesse', 'Brian']
var code_names = ['The Wo', 'Darth Lincoln', 'The Distiller']

for(var i = 0; i < names.length; i++){
    names[i] = code_names[rand(0, names.length)]
}

The FP way:

var names = ['Matt', 'Jesse', 'Brian']
var code_names = ['The Wo', 'Darth Lincoln', 'The Distiller']

names = names.map(function(v, i, arr){
    return code_names[rand(0, arr.length)]
})

Reducing

Like Map, Reduce takes a function and a collection of items. It returns a value that is created by combining the items.

Example 1, "get a sum of a collection":

[1, 2, 3, 4, 5].reduce(function(a, v){
    return a + v
}, 0)
// --> 15

In the example above, v represents each item in the array, a represents the accumulator. The accumulator is given the value 0 to start.

In ES6 this can be even more terse:

[1, 2, 3, 4, 5].reduce((a, v) => a + v, 0)

This can easily be combined with Map, for instance, to get the combined length of all names in a collection.

Example 2, "get combined length of all names":

['Matt', 'Jesse', 'Brian'].map(length).reduce(function(a, v){
    return a + v
}, 0)
// --> 14

In ES6 this can be even more terse:

['Matt', 'Jesse', 'Brian'].map(length).reduce((a, v) => a + v, 0)
// --> 14

Example 3, "count occurence of 'he' in a book passage":

var passage = "He ran a few miles before coming to the edge of a field, where he
rested upon a great, gnarled oak tree the age of Earth itself."

passage
    .toLowerCase()
    .replace(/,/g, '')
    .split(' ')
    .reduce(function(a, v){
        return a + (v.indexOf('he') !== -1 ? 1 : 0)
    }, 0)
// --> 5

And again, in ES6 this can be even more terse:

passage
    .toLowerCase()
    .replace(/,/g, '')
    .split(' ')
    .reduce((a, v) => a + (v.indexOf('he') !== -1 ? 1 : 0), 0)
// --> 5

Filter

Filter is built-in to Arrays for ES5+ as well, and it also takes a function and collection of items. Filter creates a new collection of items. It passes each item in the old collection to the function. If the function returns a "truthy" value, that item is inserted into the new collection.

function len5(v){
    return v.length >= 5
}

['Matt', 'Jesse', 'Brian'].filter(len5)
// --> ['Jesse', 'Brian']

In ES6 this can be even more terse:

['Matt', 'Jesse', 'Brian'].filter((v) => v.length >= 5)
// --> ['Jesse', 'Brian']

Why Use Map and Reduce?

  • Often one-liners
  • The important parts of the iteration (collection, operation, return value) are always in the same places in every map and reduce
  • Outside data not affected, new copies of data returned. By convention, Map and Reduce are functional and don't modify data.
  • Map and Reduce are elemental operations. They are small building blocks that combined into complex algorithms, and can more-readily be translated into English.
  • There are many friends of Map and Reduce that provide useful, basic behavior for various needs. These each use Map and/or Reduce in their code:
    • Filter
    • All
    • Any
    • Reject
    • Find
    • Pluck