For a long time, I hated seeing functions like this: const someFn = a => b => a + b;
. I thought this was just “code golf” (the idea of reducing a function to its shortest incarnation) without concern for how it would be received by the reader of the code later.
This can definitely be true and I’m still generally opposed to golf for the sake of itself. But, what I missed was that writing functions in this way, that is - using currying, can actually be really helpful.
Currying enables a greater control over what a function is doing (by reducing each function’s scope) by leveraging the composability of functions.
Let’s start with an example of addition with function declarations. Later, we’ll move into ES6 and function expressions.
The function’s purpose is trivial, but it was this example that helped me see how currying worked!
function addOne(a) {
return a + 1
}
function addNums(a, b) {
return a + b
}
function addNumsCurried(a) {
return function addBy(b) {
return a + b
}
}
If we know we always want to add by one, addOne
is perfectly reasonable. If we are okay always setting two variables, we can use addBy
. addByCurried
seems to be fundamentally different, but it actually allows us to determine what we want to add by separately from our base.
So, we could have the following
const addByTwo = addNumsCurried(2)
const addByThree = addNumsCurried(3)
console.log(
`The typeof addByTwo and addByThree --> `,
typeof addByTwo,
typeof addByThree
) // The typeof addByTwo and addByThree --> function function
It’s important to note that at the point of assignment, addByTwo
and addByThree
are functions.
This is great because it means that we invoke them! We can see this by hopping back into our console and testing it:
console.log(addByTwo) //
// ƒ addBy(b) {
// return a + b;
// }
Specifically, they are the function addBy
which takes a single parameter.
addByTwo(3) // 5
addByThree(3) // 6
Okay, now let’s transition to function expressions and ES6 (for ease of comparison, I’m assuming we’re in a totally new global scope, so we won’t have any name collision issues or previously assigned const
variables):
const addOne = a => a + 1
const addNums = (a, b) => a + b
const addNumsCurried = a => b => a + b
Wait, what?
AddNumsCurried
takes advantage of two syntactic sugar features that arrow functions provide:
- If there is only one parameter, parentheses (
()
) are optional - If the return statement is only one line, there’s an implicit return and braces (
{}
) are not necessary
That means addNumsCurried
could alternatively be written as:
const addNumsCurriedAlt = (a) => {
return (b) => {
return { a + b }
}
}
This looks pretty similar to how we had it with function declarations. That’s the point!
What if we take it one step further and use our new adding prowess to the elements of an array?
const addOneToEachBasic = ar => ar.map(num => num + 1)
const addOneToEachCompartmentalized = ar => ar.map(num => addOne(num))
const addOneCurried = ar => ar.map(addOne)
Personally, the difference between addOneToEachComparatmentalized
and addOneCurried
is when the light bulb when off! I’d run into this issue a ton with .reduce
where I wanted to separate my reducer and define it separately, but I always ran into trouble!
It wasn’t until I saw these two side by side producing the same results that I got a better understanding of what was happening.
Let’s throw in a wrinkle: Our array is full of numbers, but they can be represented as strings or numbers (but always one or the other). To check we can use a ternary to check the type. We’ll assign the anonymous function to the variable ensureNum
.
// add type checking to make sure everything is a number
const ensureNum = val => (typeof val == 'string' ? Number(val) : val)
We want to do that before we add:
const addOneToEachWithType = ar => ar.map(ensureNum).map(num => num + 1)
const addOneToEachWithTypeAndCurry = ar => ar.map(ensureNum).map(addOne)
Last step: Let’s now say we want to not just add by one, but any number. We can use our same currying techniques from function declarations to write the function expression in the following way.
const addByToEachWithType = (ar, by) =>
ar.map(ensureNum).map(addNumsCurried(by))
H/t to Jacob Blakely and his great write up on currying - which served as both the inspiration for this exercise and my guide.
Top comments (1)
Thanks for writing this.
I learned about currying in school, but in the years since I've mostly written in the prevailing "objects plus side effects" style. In my experience, even though languages like JS and Ruby have first-class functions, one mostly writes methods there, and pure functions aren't the norm, so you don't get a ton of opportunities to make use of currying. But I'm trying now to revisit FP, immutable data, etc., in my free time (largely because of this talk).
I like the
const addOneCurried = ar => ar.map(addOne)
example. Another nice thing trick is (assuming map is a function rather than a method) to currymap
with your array, so then you can do things like:curriedMap(compose(increment, square, ...))
.The gold standard, IMO, is when the language automatically does partial application, but it's pretty good if you can get a curried version of a non-curried function on the spot, e.g.
curry(suchWow)
in JS with the Ramda library. I don't like baking it into the function definition, since you lose the ability to call it with all of the arguments at once.(Clojure takes a neat approach -- it doesn't do automatic partial evaluation, but you can curry with eg.
(partial such_wow)
and there's a macro called->>
which enables a pointfree style.)FWIW I really like Professor Frisby's Mostly Adequate Guide... as an introduction to currying & pointfree style in JS. Chapters 3-5 are the relevant ones. There are some live in-browser exercises too.