JavaScript Curry: What Are the Practical Applications

JavaScript curry: what are the practical applications?

@Hank Gay

In response to EmbiggensTheMind's comment:

I can't think of an instance where currying—by itself—is useful in JavaScript; it is a technique for converting function calls with multiple arguments into chains of function calls with a single argument for each call, but JavaScript supports multiple arguments in a single function call.

In JavaScript—and I assume most other actual languages (not lambda calculus)—it is commonly associated with partial application, though. John Resig explains it better, but the gist is that have some logic that will be applied to two or more arguments, and you only know the value(s) for some of those arguments.

You can use partial application/currying to fix those known values and return a function that only accepts the unknowns, to be invoked later when you actually have the values you wish to pass. This provides a nifty way to avoid repeating yourself when you would have been calling the same JavaScript built-ins over and over with all the same values but one. To steal John's example:

String.prototype.csv = String.prototype.split.partial(/,\s*/);
var results = "John, Resig, Boston".csv();
alert( (results[1] == "Resig") + " The text values were split properly" );

Javascript: Use cases of currying

Its usefull when doing partial application or composing. You can do something like this for example

const arr = [1,2,3,4]

const curry = (fn, ...a) => fn.length > a.length ? (...b) => curry(fn,...a.concat(b)) : fn.apply(null,a)

const add = curry((a,b) => a+b)

console.log(arr.map(add(5)))

const compose = (...fns) => fns.reduce((f,g) => b => f(g(b)))

console.log(arr.map(compose(add(5),add(2))))

What are real use cases of currying?

Real use case of currying is partial application.

Currying by itself is not terribly interesting. What's interesting is if your programming language supports currying by default, as is the case in F# or Haskell.

You can define higher order functions for currying and partial application in any language that supports first class functions, but it's a far cry from the flexibility you get when every function you get is curried, and thus partially applicable without you having to do anything.

So if you see people conflating currying and partial application, that's because of how closely those concepts are tied there - since currying is ubiquitous, you don't really need other forms of partial application than applying curried functions to consecutive arguments.

What are the practical advantages of currying?

First, it's very common to mistake partial function application for currying. See this for example (I'm sure there are better resources describing it, but this was the first one i found). I've almost never seen anyone use currying in practice (except for languages like Haskell, where every function is curried by the language itself, so to speak, but even that is in order to enable simple partial function application). Partial function application on the other hand is quite useful in many languages.

Anyway, assuming you're talking about partial function application (since that's what most people are talking about when they're asking about currying), the concept is not quite as natural in C++ as in a (purely) functional language, such as Haskell for example.

For example, here we define a function sum that takes an array of numbers list and sums all the numbers together. If you're unfamilir with the concept of fold (or reduce or inject, as it is sometimes called), read this. Anyway, it would look like this:

sum list = foldl (+) 0 list

But wait a minute. We could shorten it by using partial function application! Instead of supplying an argument, we just say that sum is a function that is equal to foldl, with + and 0 partially applied.

sum = foldl (+) 0

Which one is easier to read? A matter of preference probably, but the latter emphazises the relation between sum and foldl more clearly in my opinion. And please take into account that this is a very simple example. I honestly don't know how to write a good example in C++, so you'll have to excuse me there. In any case, what is the practical advantage? Readability. Clearer intent. Shorter code.

Disclaimer: If you actually wanted to know the advantages of currying (as opposed to partial function application) I'm sorry to have made you read all this. But on the other hand, if you understand the difference between the two will also understand that currying is a great way to implement partial function application.

What is 'Currying'?

Currying is when you break down a function that takes multiple arguments into a series of functions that each take only one argument. Here's an example in JavaScript:

function add (a, b) {
return a + b;
}

add(3, 4); // returns 7

This is a function that takes two arguments, a and b, and returns their sum. We will now curry this function:

function add (a) {
return function (b) {
return a + b;
}
}

This is a function that takes one argument, a, and returns a function that takes another argument, b, and that function returns their sum.

add(3)(4); // returns 7

var add3 = add(3); // returns a function

add3(4); // returns 7
  • The first statement returns 7, like the add(3, 4) statement.
  • The second statement defines a new function called add3 that will
    add 3 to its argument. (This is what some may call a closure.)
  • The third statement uses the add3 operation to add 3 to 4, again
    producing 7 as a result.

What is the difference between currying and partial application?

Currying is converting a single function of n arguments into n functions with a single argument each. Given the following function:

function f(x,y,z) { z(x(y));}

When curried, becomes:

function f(x) { lambda(y) { lambda(z) { z(x(y)); } } }

In order to get the full application of f(x,y,z), you need to do this:

f(x)(y)(z);

Many functional languages let you write f x y z. If you only call f x y or f(x)(y) then you get a partially-applied function—the return value is a closure of lambda(z){z(x(y))} with passed-in the values of x and y to f(x,y).

One way to use partial application is to define functions as partial applications of generalized functions, like fold:

function fold(combineFunction, accumulator, list) {/* ... */}
function sum = curry(fold)(lambda(accum,e){e+accum}))(0);
function length = curry(fold)(lambda(accum,_){1+accum})(empty-list);
function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list);

/* ... */
@list = [1, 2, 3, 4]
sum(list) //returns 10
@f = fold(lambda(accum,e){e+accum}) //f = lambda(accumulator,list) {/*...*/}
f(0,list) //returns 10
@g = f(0) //same as sum
g(list) //returns 10

Currying: practical implications

First of all, I would like to stress, that due to compiler optimizations the two functions above will be compiled into the same assembly code. Without the optimizations, the cost of currying would be too high, i.e., an application of a curried function would require allocating an amount of closures equal to the number of arguments.

In practice, curried function is useful, to define partial application. For example, cf.,

let double = foo 2
let double2 x = foo2 (2,x)

Another implication is that in a curried form, you do not need to allocate temporary tuples for the arguments, like in the example above, the function double2 will create an unnecessary tuple (2,x) every time it is called.

Finally, the curried form, actually simplifies reasoning about functions, as now, instead of having N families of N-ary functions, we have only unary functions. That allows, to type functions equally, for example, type 'a -> 'b is applicable to any function, e.g., int -> int, int -> int -> int, etc. Without currying, we would be required to add a number arguments into the type of a function, with all negative consequences.

Functional programming when to curry

There are different schools of thought on this, one is not necessarily more "functional" than the others. But first:

Libraries like ramda have a curry function that supports both syntaxes:

const add = R.curryN(2, (x, y) => x + y);
add(2)(3); // 5
add(2, 3); // 5

So don't get too hung up on the difference.

There's a fairly important idiom that you can't really do while currying in JavaScript: varargs. I mean, you might be able to find a (laborious, convoluted) way to make it work, but generally you wouldn't. So if a function can take any number of arguments, it shouldn't be curried.

The next concern is performance.

That's a lot of extra function calls compared to e.g.

const add = (x, y) => x + y;
const add3 = add.bind(null, 3);

With the first version, every call to add pays the price. With this one only the partially applied version does, and it uses a built in (likely faster) to boot.

Finally, a word about contracts:

When you expose functionality to anyone other than yourself (co-workers, customers, open source users), you are making a contract with them that this is the way your function works. If you find that you need to change it later, there are changes that break your existing callers and ones that don't. No one cares about the latter, everyone will curse you for the former.

Going from uncurried to curried (via e.g. ramda) is a non-breaking change. Your existing callers will still work. The same is not true of the reverse.

Given all that, I would probably not curry any functions by default unless I'm very very sure that I will be calling them piecemeal every time. Remember, your callers (even if that's you from a different module) can always curry it themselves!



Related Topics



Leave a reply



Submit