Making Arrays Stuff More Functional

We previously covered 2 "classes" of Array methods

  • Mutator Methods - Those that change the structure of an Array 
  • Accessor Methods - Those that access contents of the Array without changing it

We have one left...

Iterator Methods

These methods iterate over the array

forEach(callback[, arg])

Calls a function you provide for each element in the Array

function logTheThings(element, index, array){
    console.log('Current element is: ', element);
}

var myArray = [3, 'taco', false, 'Wonka'];

myArray.forEach(logTheThings);

//Current element is: 3
//Current element is: 'taco'
//Current element is: false
//Current element is: 'Wonka'

Your callback function takes 3 params:

  • element - the current element being iterated over
  • index - the current index of the element in the Array
  • array - the array itself being traversed
var myArray = [3, 'taco', false, 'Wonka'];

myArray.forEach(function(element, index, array) {
    console.log('Current element is: ', element);
});

//Current element is: 3
//Current element is: 'taco'
//Current element is: false
//Current element is: 'Wonka'

=>

TIP: If you don't want the function to run against an element return false

every(callback[, arg])

Returns true if every element in the Array passes the test you provide

Your callback function takes 3 params:

  • element - the current element being iterated over
  • index - the current index of the element in the Array
  • array - the array itself being traversed
function isNumeric(element, index, array) {
    return !isNaN(parseInt(element));
}

var myArray = [3, 'taco', false, 'Wonka'];

myArray.every(isNumeric); //return false

var myOtherArray = [3, 5, 6, 9];

myArray.every(isNumeric); //return true

some(callback[, arg])

Returns true if ANY element in the Array passes the test you provide

Your callback function takes 3 params:

  • element - the current element being iterated over
  • index - the current index of the element in the Array
  • array - the array itself being traversed
function hazNumber(element, index, array) {
    return !isNaN(parseInt(element));
}

var myArray = [3, 'taco', false, 'Wonka'];

myArray.some(hazNumber); //return true, 1 iteration

var myOtherArray = ['test', 'taco', 'hotdog', {}];

myOtherArray.some(hazNumber); //return false, 4 iterations

Returns immediately when first true is found

filter(callback[, arg])

Creates and returns a new array with all elements that pass true test

Your callback function takes 3 params:

  • element - the current element being iterated over
  • index - the current index of the element in the Array
  • array - the array itself being traversed
function getNumberz(element, index, array) {
    return !isNaN(parseInt(element));
}

var myArray = [3, 'taco', false, 44, 'Wonka'];

myArray.filter(getNumberz); //return [3, 44]

console.log(myArray); //[3, 'taco', false, 44, 'Wonka']

var myOtherArray = ['test', 'taco', 'hotdog', {}];

myOtherArray.filter(getNumberz); //return []

console.log(myOtherArray); //['test', 'taco', 'hotdog', {}]

NOTE: filter() does NOT mutate the original array

map(callback[, arg])

Creates/returns a new array with all returned values from callback function

Your callback function takes 3 params:

  • element - the current element being iterated over
  • index - the current index of the element in the Array
  • array - the array itself being traversed
function tripleIt(element, index, array) {
    return element * 3;
}

var myArray = [3, 12, 1, 9];

myArray.map(tripleIt); //return [9, 36, 3, 27]

console.log(myArray) //[3, 12, 1, 9]

var myOtherArray = [3, 12, 1, 'taco'];

myOtherArray.map(tripleIt); //return [9, 36, 3, NaN]

console.log(myArray) //[3, 12, 1, 'taco']

NOTE: map() does NOT mutate the original array

reduce(callback[, initalValue])

Returns a single value accumulated against all values from callback function

Your callback function takes 4 params and 1 optional param:

  • previousValue - the previous element being iterated over
  • currentValue - the current element being iterated over
  • index - the current index of the array
  • array - the array itself being traversed
function summarize(previousValue, currentValue, index, array) {
    return previousValue + currentValue;
}

var myArray = [3, 12, 1, 9];

myArray.reduce(summarize); //return 25

console.log(myArray) //[3, 12, 1, 9]

//Start at a certain initial value
myArray.reduce(summarize, 10); //return 35

console.log(myArray) //[3, 12, 1, 9]

NOTE: reduce() does NOT mutate the original array

If you pass NO optional param, first iteration is at index 1 so that previous/next have values

//Omit index and array is still valid

function summarize(previousValue, currentValue) {
    return previousValue + currentValue;
}

var myArray = [3, 12, 1, 9];

myArray.reduce(summarize); //return 25

console.log(myArray) //[3, 12, 1, 9]

//Start at a certain initial value
myArray.reduce(summarize, 10); //return 35

console.log(myArray) //[3, 12, 1, 9]

reduceRight(callback[, initalValue])

Returns a single value accumulated against all values from callback function

Your callback function takes 4 params and 1 optional param:

  • previousValue - the previous element being iterated over
  • currentValue - the current element being iterated over
  • index - the current index of the array
  • array - the array itself being traversed

NOTE: reduceRight() does NOT mutate the original array

reduceRight() has the same functionality as reduce() except that it starts from the right side of the input array

References

Javascript is a

multi-paradigm

language

  • Imperative
  • Functional

Imperative/ Procedural

How does it work?

 

Defines computation as statements that change a program state.

Functional/ Declarative

What does it do?

 

Express the desired result by describing rules, constraints, and relationships-- without explicitly mutating state.

Before computers were invented, mathematicians invented two

Universal Models of Computation

(1) Turing Machines

(state visualized as a ticker tape of infinite length)

(2) Lambda Calculus

(no state, just functions)

Turing eventually proved both Universal Models of Computation are mathematically equivalent - both can express any computable algorithm

As it turned out, Turing Machines very closely matched the actual hardware of computers. Instead of infinite ticker tape, real computers have finite memory, but everything else is the same.

Imperative programming became by far the dominant paradigm.

Lambda calculus was majorly sidelined for decades... until...

Intro to Lambda Calculus

Javascript!

brought functional programming to the masses

JS was the first popular programming language combining the power of "higher order functions" with C-like syntax most programmers are familiar with.

 

"Higher order functions" means functions can take functions as parameters and can return functions as output.

Functions are values

This is a function that returns a function

 

const greaterThan = a => b => b > a

// use it like this:
const greaterThan10 = greaterThan(10)
console.log(greaterThan10(11)) // true

This is a function that takes

a function as a parameter

 

const maybeDouble = (predicate, n) => 
  predicate(n) ? n * 2 : n

// use it like this:
const isOdd = n => n % 2 === 1
console.log(maybeDouble(isOdd, 5)) // 10

btw, "predicate" is just a fancy word for a function that returns only true or false. There are lots of fancy words in functional programming for things that are actually quite simple. Don't be intimidated.

Composition

Suppose we have two functions:

const addTwo = n => n + 2
const multiplyByFour = n => 4 * n

Note that there are two ways to compose them:

const multiplyByFourThenAddTwo = 
  x => addTwo(multiplyByFour(x))

const addTwoThenMultiplyByFour = 
  x => multiplyByFour(addTwo(x))

Text

Text

The cornerstone of functional programming is...

Problem

Manually invoking functions in series is tedious, error-prone, and difficult for code re-use.

Consider:

 

func1(func2(func3(func4(func5(func6(func7(x)))))))

Can we do better?

const a = func7(x)
const b = func6(a)
const c = func5(b)
const d = func4(c)
const e = func3(d)
const f = func2(e)
const g = func1(f)

Or:

const funcs = [addTwo, multipleByFour]

Imagine an array of functions...

How can we compose them in the general case?

const compose = (...funcs) => input =>
  funcs.reduceRight((prevOutput, nextFunc) => 
    nextFunc(prevOutput), input)

const pipe = (...funcs) => input =>
  funcs.reduce((prevOutput, nextFunc) => 
    nextFunc(prevOutput), input)

compose(...funcs)(4) // 18, right-to-left
pipe(   ...funcs)(4) // 24, left-to-right

// imagine beging able to rewrite this:
const myNumber = Math.round(Math.sqrt(Math.PI))

// as this:
const myNumber = Math.PI |> Math.sqrt |> Math.round

// other symbols considered for the pipeline operator:
// :: -> ~> ..

Sidenote: did you know future javascript might have a pipeline operator?

Test it out here

Most programmers prefer pipe over compose

Although some mathematicians prefer compose

(it's sort of like reverse polish notation),

pipe is like English

(and many other languages):

data flows left-to-right, top-to-bottom.

Notice something about the functions you pass to compose and pipe?

They all have exactly one parameter.

 

In functional programming, we say they all have an arity of one (unary).

 

Arity is just a fancy word for the number of parameters a function has.

 

In pure functional programming

(lambda calculus), all functions have an arity of exactly one.

Currying

The next concept you are likely to run into in functional programming is

Named after Haskell Curry, mathematician

Currying is the process of converting a function, that accepts n arguments, into a sequence of n chained function calls, having exactly one argument each.

// uncurried
const multiply = (a, b) => a * b
multiply(2, 3) // 6
// curried
const curriedMultiply = a => b => a * b 
curriedMultiply(2)(3) // 6

Can we curry automagically?

Glad you asked...

const curry = (f, arr = []) => (...args) => (
  a => a.length === f.length 
    ? f(...a) 
    : curry(f, a)
)([...arr, ...args])

// currying gives us SUPERPOWERS, like this:
const multiply = (a, b) => a * b
const multiplyBy4 = curry(multiply)(4)
multiplyBy4(3) // 12

// now we can convert any multi-arity function
// into a Function Factory :)

Going back to our original example

// remember this?
// const funcs = [addTwo, multipleByFour]

// now we can refactor it like this:
const add = (a, b) => a + b
const multiply = (a, b) => a * b
const addTo = curry(add)
const multiplyBy = curry(multiply)

const funcs = [addTo(2), multiplyBy(4)]
pipe(...funcs)(4) // 24

But wait there's more...

A lot more. If you look at libraries like

ramda.js, rambda, lodash/fp, rxjs

you will see a ton of utility functions.

Good news: you don't need to learn them all.

Most of the time a small set will do everything you need.

So how do you choose which functional library to use?

Lodash is by far the most popular library, but lodash/fp is comparatively little-known. lodash/fp functions are wrappers around the better known non-fp lodash functions, so if you are already using lodash, lodash/fp is probably your best choice.

 

Ramda is a little more hardcore functional than lodash/fp because most of the functions assume currying. If you are a functional geek, you might enjoy ramda over lodash/fp.

 

Rambda bills itself as smaller and faster than ramda, but it is probably the most obscure choice, and has the fewest utility functions.

 

Making Stuff More Functional

By Jason Sewell

Making Stuff More Functional

  • 1,429