Senior Software Engineer at Fastly
Lover of JavaScript, hater of semicolons, and maintainer of a glorious beard.
Host of Second Career Devs podcast
A pain in the ass to define. Kinda.
The formal definition is:
A style of building the structure and elements of computer programs that treats computation as the evaluation of mathematical functions.
Unfortunately, that definition isn't very helpful.
It's not that different from telling you that the definition of painting is to apply paint to a surface.
But, we can define different styles of painting by examining the characteristics that differentiate them.
function doubleEach (arr) {
const results = []
for (let i = 0; i < arr.length; i++) {
result.push(arr[i] * 2)
}
return results
}
const doubles = doubleEach([1,2,3])
function doubleEach (arr) {
return arr.map(x => x * 2)
}
const doubles = doubleEach([1,2,3])
const double = x => x * 2
const map = fn => xs => xs.map(fn)
const doubleEach = map(double) // returns a fn
const doubles = doubleEach([1,2,3])
A pure function is one that, given the same inputs, will always return the same output without any side effects.
// Pure Function
function add (x, y) {
return x + y
}
// Impure Example
let nextId = 0
function impureAdd (x, y) {
nextId++ // Ahh, gross mutation!
return x + y
}
Purity leads to easily testable functionality.
It also creates trustworthy contracts between our functions, a fact we will soon utilize.
(super smart dude)
Curry
(super delicious food)
Currying is the technique of refactoring a function that normally accepts multiple arguments into one that accepts them one at a time.
function curriedAdd (x) {
return function (y) {
return x + y
}
}
// Or more elegantly with arrow functions
const curriedAdd = x => y => x + y
Let's take a simple function and curry it.
// Pretend we imported `add` from before
const add5 = add(5)
// Now we have a new fn with `5` partially applied
// We can use add5 as many times as we want
console.log(add5(10)) // 15
console.log(add5(-5)) // 0
console.log(add5(45)) // 50
Because a curried function can store some of its arguments in closure. This is called partial application.
Argument order is very important when currying functions. Generally, you want the most unstable argument to be passed last.
const filter = fn => arr => arr.filter(fn)
const lessThanTen = filter(x => x < 10)
const arr = [1, 5, 20, 11, 3, 10]
const arr2 = [3, 12, 14, 15, 2, 1]
const filteredArr1 = lessThanTen(arr) // [1, 5, 3]
const filteredArr2 = lessThanTen(arr2) // [3, 2, 1]
Think back to high school.
Not the shitty parts you wish you could forget. But the math parts that you probably did forget.
Remember this at all? f(x) = y
In math, a function always maps one value to another aka pure functions.
You can also give the result of a function as the input to another function aka higher order functions. This is called composition.
It looks like: f(g(x)) = y
In programming, we rarely name functions a single letter. If you do, please stop, mkay?!
Thus, nesting functions to create compositions becomes really ugly, really fast.
const scream = str => str.toUpperCase()
const exclaim = str => `${str}!`
const repeat = str => `${str} ${str}`
const string = 'Bob, the world is coming to an end'
const warnBob =
repeat(exclaim(scream(string)))
console.log(warnBob)
// BOB, THE WORLD IS COMING TO AN END!
// BOB, THE WORLD IS COMING TO AN END!
Instead of nesting functions, what if we used currying and partial application to create a function that would do the composition for us?
Let me introduce the compose function.
const compose = (...fns) => x =>
fns.reduceRight((acc, fn) => fn(acc), x)
The compose function takes any number of functions as arguments and returns a function awaiting the value on which to operate.
Our new function feeds the output of the previous function as the input to the next one.
To read compose, you have to read the functions right-to-left.
// Pretend we import `compose`, `scream`,
// `exclaim`, and `repeat`
const warnAdamantly = compose(
repeat,
exclaim,
scream
)
const string = 'Bob, the world is coming to an end'
const warnBob = warnAdamantly(string)
console.log(warnBob)
// BOB, THE WORLD IS COMING TO AN END!
// BOB, THE WORLD IS COMING TO AN END!