Currying in Javascript and its practical usage
What is currying?
Currying is the process of transforming a function which takes multiple parameters into a sequence of functions, which each of them takes one parameter at a time. It’s a special type of Partial Function Application. In pure functional programming language such as ML or Haskell, functions are defined in curried form by default. Let’s see how it work in Javascript.
// A normal js function
const sum = (a, b) => a + b
sum(1, 2) // 3
// Curried version of the sum function above
const curriedSum = a => b => a + b
curriedSum(1)(2) // 3
The curriedSum
function above return a function instead of final result. The returned function contains a
in its closure scope, accept the other parameter in the list (b
), and actually calculate for final result. Sounds good? Now, how will it be with more parameters?
// Normal sum function with 3 parameters
const sum = (a, b, c) => a + b + c
sum(1, 2, 3) // 6
const curriedSum = a => b => c => a + b + c
curriedSum(1)(2)(3) // 6
It does not matter on how many parameters the function needs. If a function has n
number of parameters, its curried version will be a chain of n
functions. Only the last function will return the desired result. The others return the next function in chain. Be noticed that each function should take only one parameter, which called Unary function
. We will discuss deeper on unary function later in other section.
Why is currying?
Currying is a type of partial function application. We can use its returned functions to make a lighter version of an existing function. It’s helpful when we have many places that use a function with exactly the same way. Our implementation will be shorter and more readable.
// Normal usage
const PERCENT_120 = 1.2
const multiply = (a, b) => a * b
const newSalary = multiply(salary, PERCENT_120)
// Currying usage
const multiply = a => b => a * b
const increase20Percent = multiply(1.2)
const newSalary = increase20Percent(salary)
Sounds cool. But it’s not the main benefit. As I mention in previous section, currying requires strictly use of unary function
. It makes a great foundation for a standard interface. When we have a set of functions implement a common interface, we can easily introduce flexible usages on them. It’s pretty similar to how Promise
defines its standard and becomes super powerful.
Here are standards for a curried function:
- It has 1 parameter
- It returns an
unary function
, which also has 1 parameter
In the scope of this article, I will talk about 2 main usages: point free style and functional composition.
Point free style
Point free style, also called Tacit Programming, is a coding style where caller does not pass arguments explicitly to a function. See two samples below. The second one explains point free style, where console.log
matches standards required by catch
.
// Normal implementation
try {
...
} catch (err => console.log(err))
// Point free style
try {
...
} catch (console.log)
The only requirement for point free style is the prototype of input function matches with spec of the parameter. This style can shorten the code and make it more readable.
// Normal implementation
fetchUser(id).then(user => {
return verifyAdminUser(user)
}).then(isAdmin => {
return render(isAdmin)
})
// Point free style
fetchUser(id).then(verifyAdminUser).then(render)
Okay. Now let’s see how we can implement this style with curried function in the sample below. The compose
function in this sample is pretty popular and appears in many libraries, a popular one is lodash. I put its implementation here incase you want to run the code on your own.
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
const sum = a => b => a + b
const multiply = a => b => a * b
const addTransactionFee = sum(2)
const addTax = multiply(1.1)
const addMonthlyPromotion = multiply(0.8)
// Result of compose function below equals to addTransactionFee(addTax(addMonthlyPromotion(100)))
const paymentAmount = compose(addTransactionFee, addTax, addMonthlyPromotion)(100) // 100 * 0.8 * 1.1 + 2 = 90
Function composition
Function composition
is a mechanism to combine simple functions to build more complicated ones. In the composed chain, result of each function is argument for the next one. Result of the last function will be the final one. In my previous sample, I composed 3 functions addTransactionFee
, addTax
, and addMonthlyPromotion
into one.
In Javascript, functions return only one value. Since this value will be argument for the next call, function has to be unary to be composable. Curried function is totally fit for composition. If you want to compose a function with more than one parameter, the best way is to curry it.
Let go back with the previous sample. Now I want to add a log function between each step to debug my code.
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
const sum = a => b => a + b
const multiply = a => b => a * b
const addTransactionFee = sum(2)
const addTax = multiply(1.1)
const addMonthlyPromotion = multiply(0.8)
const log = a => {
console.log(a)
return a
}
const paymentAmount = compose(
log,
addTransactionFee,
log,
addTax,
log,
addMonthlyPromotion
)(100)
// Output:
// 80
// 88
// 90
In the sample above, I added a new function log
. This function simply added a log before returning the input argument. Notice that I can’t compose console.log
directly because this function does not return anything. Everything seems OK now. But, what happen if I wanna log a custom label for each step? The log function will receive another parameter:
const log = (label, value) => {
console.log(label, value)
return value
}
This function is no longer composable as it takes 2 parameters. It’s time for currying. Here is the complete code:
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
const sum = a => b => a + b
const multiply = a => b => a * b
const addTransactionFee = sum(2)
const addTax = multiply(1.1)
const addMonthlyPromotion = multiply(0.8)
const log = label => value => {
console.log(label, value)
return value
}
const paymentAmount = compose(
log('addTransactionFee'),
addTransactionFee,
log('addTax'),
addTax,
log('addMonthlyPromotion'),
addMonthlyPromotion
)(100)
// Output:
// addMonthlyPromotion 80
// addTax 88
// addTransactionFee 90
Conclusion
In this article, I’ve walked through the definition of currying and some of its practical usages. In my opinion, the most useful one is for function composition. Feel good? If you want to get deeper understanding, I would recommend the book Composing Software
from Eric Elliott. He did a great explanation for currying in terms of Mathematics and functional programming.
I may come back with another article about some tips for currying sometime later. See you next time.