How simple functional programming patterns can help you write more expressive code
Learn how to re-use existing functions in your codebase to expressively create new ones with the help of currying, partial application, and higher-order functions.
Pure Function
A pure function is a function that's total, deterministic and causes no observable side-effect. That’s pretty vague, right? Then, let's elaborate on these characteristics a little bit.
A total function always produces a valid output for every acceptable input. In other words, it should never return null or throw an exception given a valid input.
Given an input, a deterministic function always produces the same output. For example, given 2 and 3 to function multiple(a, b) { return a * b; }
, the output will always be 6.
Pure functions should never ever access or modify state outside of its scope. If you directly reference window
, global
, console
, call an API, change the DOM or change variables outside the scope of a function, this function is not pure.
Currying
After you curry a pure function, you can call it with as many arguments at a time as you want. If you call a curried function with fewer arguments than it takes, the curried function produces a new function.
// add takes 2 arguments as input at once
function add(a, b) {
return a + b
}
// curriedAdd takes 2 arguments as input one or more at a time.
const curriedAdd = curry(add)
// add1 takes one argument as input
const add1 = curriedAdd(1)
// result is 3
const result = add1(2)
For now, just keep in mind that you curry a function to prepare it for partial application and composition (composition is the next functional programming concept you should learn after reading this note).
Partial Application
Partially apply arguments to a curried function to create more specialized ones. Assuming add
is already curried, we can re-use it to create a couple of more specialized functions:
const increment = add(1)
const decrement = add(-1)
increment(10) // 11
decrement(10) // 9
Note how we re-used add
to implement increment
and decrement
. Can you think of any function in the codebase you’re working on right now, that you can curry and partially apply arguments to create more specialized functions?
Can you explain in simple words what’s the difference between currying and partial application?
Higher-Order Functions
HOF is simply a function that takes another function as input. Array.prototype.map
, Array.prototype.filter
, Array.prototype.reduce
, Array.prototype.sort
, etc… are all examples of higher order functions.
const numbers = [5, 3, 2, 7]
function asc(a, b) {
return a - b
}
// sort numbers in ascending order
// output: [2, 3, 5, 7]
numbers.sort(asc)
const addresses = [
{ street: "5th Avenue", number: 10, city: "New York", country: "USA" },
{ street: "Av. Paulista", number: 100, city: "São Paulo", country: "Brazil" },
]
function toDescription({ number, street, city, country }) {
return `${number} ${street}, ${city} ${country}`
}
// maps address object to description string
// output: [ '10 5th Avenue, New York USA', '100 Av. Paulista, São Paulo Brazil' ]
addresses.map(toDescription)
Putting it all together
const people = [
{ name: "John Doe", gender: "male" },
{ name: "Jane Doe", gender: "female" },
]
const propEq = curry(
(propName, propValue, object) => object[propName] === propValue
)
const isMale = propEq("gender", "male")
const isFemale = propEq("gender", "female")
const filter = curry((fn, list) => list.filter(fn))
const femaleOnly = filter(isFemale)
// output: [{ name: “Jane Doe”, gender: “female” }]
femaleOnly(people)
Thanks for reading! Have something to say? Please, leave a comment!