Functional Programming: currying and partial application

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 less 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 = init
for (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(add, 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'))))

Comments