Currying reimagined
Table of Contents
What is currying? #
Currying is the process of transforming a function that takes multiple arguments in a tuple as its argument, into a function that takes just a single argument and returns another function which accepts further arguments, one by one
E.g. it’s process of converting funtion like that:
const func = (x, y, z) => [x, y, z];
func(1, 2, 3) === [1, 2, 3];
to
const func = (x) => (y) => (z) => [x, y, z];
func(1)(2)(3) === [1, 2, 3];
Other way to see it is those two representation are equivalent. As well as those:
const func = (x, y) => (z) => [x, y, z];
const func = (x) => (y, z) => [x, y, z];
Which brings us to “auto-currying” or partial application. Imagine if you provided not enough arguments for a function call, like this
const func = (x, y, z) => [x, y, z];
func(1, 2);
The system can automatically transform function to equivalent function, which takes the required number of arguments and call it with given arguments
// original function transformed to (x, y) => (z) => [x, y, z];
// where x = 1 and y = 2
// so the final version is (z) => [1, 2, z];
func(1, 2)(3) === [1, 2, 3];
// the same as
func(1)(2, 3) === [1, 2, 3];
Historical note: Currying and curried functions are named after Haskell B. Curry. While Curry attributed the concept to Schönfinkel, it had already been used by Frege (citation needed).
Practical usage #
From practical point of view partial application requires less boilerplate (less closures). For example, if we have following code:
// Let's assume we have a sort function similar to this
const sort = (comparator, arr) => arr.sort(comparator);
// but we can't change implementation, for example,
// imagine it works with a linked list instead of JS array
const sortIncrementaly = (arr) => sort((x, y) => x - y, arr);
With partial application this code requires less boilerplate:
const sortIncrementaly = sort((x, y) => x - y);
Discomfort points #
Currying and partial application have the following discomfort points:
- It relies on positional arguments e.g.
(1, 2, 3)
instead of named arguments(x: 1, y: 2, z: 3)
- It needs the “subject” argument to be the last one in the list of arguments
Positional arguments are hard to remember (especially if there are more than 2 of them). For example, without looking into the manual, can you tell what the second argument stands for:
JSON.stringify(value, null, 2);
It is easier to work with named params:
JSON.stringifyNamedParams({ value, replacer: null, space: 2 });
Functions with the “subject” argument, in the end, works better for currying. For example, lodash’es and underscore’s map
function:
_.map(arr, func);
doesn’t work with _.curry
out of the box. There is _.curryRight
and _.curry
with placeholders. It would work better if arguments would be another way around (_.map(func, arr)
).
Reimagine #
Currying, as idea, came from math, which has rigid idea of function. In programming we have more “free” definition. We can have:
- optional arguments:
(x, y = 2) => ...
- varied length of arguments:
(x, ...y) => ...
- named arguments:
({ x, y }) => ...
How would currying work for named params?
const func = ({ x, y, z }) => [x, y, z];
const curriedFunc = curry(func);
curriedFunc({ x: 1 })({ y: 2 })({ z: 3 }); // [1, 2, 3]
curriedFunc({ z: 3 })({ y: 2 })({ x: 1 }); // [1, 2, 3]
curriedFunc({ z: 3, y: 2 })({ x: 1 }); // [1, 2, 3]
// ...
There are no problems to remember the order of arguments. Arguments can be partially applied in any order.
Just for fun I implemented this function in JavaScript: source code
Feedback? #
Would you use the partial application more if it would be natively supported in your programming language?
Read more: Awesome Tagged Template Literals, Metaprogramming