Currying reimagined

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

https://wiki.haskell.org/Currying

E.g. it’s process of converting funtion like that:

1const func = (x, y, z) => [x, y, z];
2func(1, 2, 3) === [1, 2, 3];

to

1const func = (x) => (y) => (z) => [x, y, z];
2func(1)(2)(3) === [1, 2, 3];

Other way to see it is those two representation are equivalent. As well as those:

1const func = (x, y) => (z) => [x, y, z];
2const 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

1const func = (x, y, z) => [x, y, z];
2func(1, 2);

The system can automatically transform function to equivalent function, which takes the required number of arguments and call it with given arguments

1// original function transformed to (x, y) => (z) => [x, y, z];
2// where x = 1 and y = 2
3// so the final version is (z) => [1, 2, z];
4func(1, 2)(3) === [1, 2, 3];
5// the same as
6func(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:

1// Let's assume we have a sort function similar to this
2const sort = (comparator, arr) => arr.sort(comparator);
3// but we can't change implementation, for example, 
4// imagine it works with a linked list instead of JS array
5const sortIncrementaly = (arr) => sort((x, y) => x - y, arr);

With partial application this code requires less boilerplate:

1const sortIncrementaly = sort((x, y) => x - y);

Discomfort points

Currying and partial application have the following discomfort points:

  1. It relies on positional arguments e.g. (1, 2, 3) instead of named arguments (x: 1, y: 2, z: 3)
  2. 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:

1JSON.stringify(value, null, 2);

It is easier to work with named params:

1JSON.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:

1_.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?

1const func = ({ x, y, z }) => [x, y, z];
2const curriedFunc = curry(func);
3curriedFunc({ x: 1 })({ y: 2 })({ z: 3 }); // [1, 2, 3]
4curriedFunc({ z: 3 })({ y: 2 })({ x: 1 }); // [1, 2, 3]
5curriedFunc({ z: 3, y: 2 })({ x: 1 }); // [1, 2, 3]
6// ...

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?

Except where otherwise noted, content on this site is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0