Comments
- Loading...
An explanation of currying and partial application using JavaScript
Let's start with a simple function that takes two numbers and adds them together:
const add = function (a, b) {
return a + b
}
console.log(add(1, 2))Now, instead of a single function that takes two arguments and returns a value, I'm going to rewrite this so that add is a function that takes a single argument, and returns another function which accepts a single argument, and then returns the sum of both arguments:
const add = function (a) {
return function (b) {
return a + b
}
}
console.log(add(1)(2))To make this a bit tidier, the same can be written with arrow functions:
const add = a => b => a + b
console.log(add(1)(2))Taking a function that accepts multiple values, and transforming it to a 'stream' of functions that each only accept a single argument is called currying.
Writing functions in this style is a bit tedious though. We can write a higher-order function curry that will take a function that accepts multiple arguments, and return a curried version of that function:
function curry(func) {
return function curried(...args) {
return args.length >= func.length
? func.apply(this, args)
: curried.bind(this, ...args)
}
}
const add = curry((a, b) => a + b)
console.log(add(1))
console.log(add(1, 2))Why go through all this trouble? Well, imagine you want to write another function that increments a number by one:
const inc = n => 1 + n
console.log(inc(2))This would work, but it's very similar to the add function we already wrote. Using the curried version of add above, writing an inc function becomes as simple as:
function curry(func) {
return function curried(...args) {
return args.length >= func.length
? func.apply(this, args)
: curried.bind(this, ...args)
}
}
const add = curry((a, b) => a + b)
const inc = add(1)
console.log(inc(2))Taking a curried function and supplying fewer arguments than it expects is called partial application.
In a previous post I talked about re-implementing reduce. Here's that reduce function, but wrapped with curry:
const reduce = curry((fn, init, arr) => {let response = initfor (let i = 0, l = arr.length; i < l; ++i) {response = fn(response, arr[i])}return response})
With this, we can take our add function from earlier, and create a sum function that adds all the numbers in an array:
function curry(func) {
return function curried(...args) {
return args.length >= func.length
? func.apply(this, args)
: curried.bind(this, ...args)
}
}
const reduce = curry((fn, init, arr) => {
let response = init
for (let i = 0, l = arr.length; i < l; ++i) {
response = fn(response, arr[i])
}
return response
})
const sum = reduce((a, b) => a + b, 0)
console.log(sum([1, 2, 3]))These are fairly boring things to curry, so I'll share a fun example of currying I used recently.
function curry(func) {
return function curried(...args) {
return args.length >= func.length
? func.apply(this, args)
: curried.bind(this, ...args)
}
}
const on = curry((f, g, a, b) => f(g(a), g(b)))
const prop = curry((key, obj) => obj[key])
const localeCompare = curry((a, b) => a.localeCompare(b))
const data = [{ id: 1, title: 'duplicate' }, { id: 2, title: 'duplicate' }, { id: 3, title: 'unique' }]
console.log(data.sort(on(localeCompare, prop('title'))))