A programming paradigm closely related to several mathematical fields such as category theory and lambda calculus (don't let that scare you!).
FP is a declarative style of programming, favoring expressions over statements. It is obsessed with functional purity and avoiding/properly handling side effects. In concert with this obsession, FP avoids changing state through mutations.
"Functions as first class citizens" refers to languages that allow functions to be assigned to variables or passed as arguments to functions.
function add (x, y) {
return x + y
}
const result = add(add(2, 3), add(4, 5))
console.log(result) // 14
A higher order function does at least one of the following: accepts a function as an argument, or returns a new function.
function emojiLogs (emoji) {
return function (...strs) {
console.log(`${emoji} ${[...strs].join(' ')}`)
}
}
const catLog = emojiLog(😻)
const dogLog = emojiLog(🐶)
const logLog = emojiLog(🌲)
catLog('Krios', 'Tali')
// 😻 Krios Tali
dogLog('<- Why do I not have one of these yet?!')
// 🐶 <- Why do I not have one of these yet?!
logLog('Douglas Firs are real nice!')
// 🌲 Douglas Firs are real nice!
Krios & Tali
Any Mass Effect fans?
A pure function is one that, given the same inputs, will always return the same output without any side effects.
// This is a pure function. Given the same inputs
// we'll always get the same output and never change
// the state outside the scope of our function
function add (x, y) {
return x + y
}
// Impure Example
let nextId = 0
function impureAdd (x, y) {
nextId++ // This is a mutation and makes this function impure!
return x + y
}
"Yeah Kyle, but we do more complicated things than add numbers together!" You're totally right!
// Impure data fetch function
function impureGetData (url) {
return fetch(url).then(response => response.json())
}
// Pure data fetch
function getData (url) {
return function () {
return fetch(url).then(response => response.json())
}
}
But why does this make it pure? What do we gain?
Purity leads to easily testable functionality.
It also creates trustworthy contracts between our functions, a fact we will soon utilize.
Thai Red Curry
Currying is the technique of refactoring a function that normally accepts multiple arguments into one that accepts them one at a time.
A curried function will continue to return a unary function for each argument until the final one is provided. Then, the function is evaluated.
The arguments provided to a curried function are said to be "partially applied" while the function awaits its final argument.
// With function keywords
function add (x) {
return function (y) {
return x + y
}
}
// A more elegant way with arrow functions
const elegantAdd = x => y => x + y
// Let's use it
const add2 = add(2) // 2 is partially applied, held in closure
const add5 = add(5) // 5 is partially applied, held in closure
const eight = add2(6) // receives 2nd argument, evals to 8
console.log(add5(eight)) // 13
Let's take a common function and curry it.
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]
If you remember high school math, composition is the act of passing the result of one function to another function.
This takes the form of `f(g(x))` where `f` and `g` are both functions. But nesting functions can be really ugly.
const scream = str => str.toUpperCase()
const exclaim = str => `${str}!`
const repeat = str => `${str} ${str}`
const warnBob =
repeat(exclaim(scream('Bob, the world is coming to an end')))
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?
// Pretend we imported `scream`, `exclaim`, and `repeat`
// from the previous slide
const compose = (...fns) => value =>
fns.reduceRight((acc, fn) => fn(acc), value)
const warnAdamantly = compose(
repeat,
exclaim,
scream
)
const warnBob = warnAdamantly('Bob, the world is coming to an end')
console.log(warnBob)
// BOB, THE WORLD IS COMING TO AN END! BOB, THE WORLD IS COMING TO AN END!