0

I love that ECMAScript 6 allows you to write curried functions like this:

var add = x => y => z => x + y + z; 

However, I hate that we need to parenthesize every argument of a curried function:

add(2)(3)(5); 

I want to be able to apply curried functions to multiple arguments at once:

add(2, 3, 5); 

What should I do? I don't care about performance.

0

3 Answers 3

5

Currying and the application of curried functions are controversial issues in Javascript. In simple terms, there are two opposing views, which I illustrate both briefly.


- Use of a separate curry function only when necessary

The adaptation of concepts from other languages or paradigms is in principle a good thing. This adaptation though, should be done with the elementary means of the target language. What does that mean for currying in javascript?

  • curried functions are called as a sequence of unary functions:add3(1)(2)(3); // 6
  • own functions are manually curried with arrows const add3 = x => y => z => x + y + z;
  • third party functions or methods are curried by a separate curry function

- Use of a separate curry implementation by default

There's a problem with the proposed $/uncurry function:

const $ = (func, ...args) => args.reduce((f, x) => f(x), func); const sum = x => y => z => x + y + z; $(sum, 1, 2, 3); // 6 $(sum, 1, 2)(3); // 6 $(sum, 1)(2, 3); // z => x + y + z 

In this way uncurried functions can only once be applied with an unlimited number of arguments. Any subsequent calls must be made unary. The function does exactly what it promises. However, it does not allow application of curried functions, such as JavaScript developers are used to. Most of the current curry implementations are more flexible. Here's an extended implementation:

const uncurry = f => (...args) => args.reduce( (g, x) => (g = g(x), typeof g === "function" && g.length === 1 ? uncurry(g) : g), f ); const sum = uncurry(x => y => z => x + y + z); sum(1, 2, 3); // 6 sum(1, 2)(3); // 6 sum(1)(2, 3); // 6 

This implementation works, if you like auto-uncurrying: Once a uncurried function itself produces a curried function as a return value, this returned function is automatically uncurried. If you prefer more control, the following implementation might be more appropriate.

The final uncurry implementation

const partial = arity => f => function _(...args) { return args.length < arity ? (...args_) => _(...args.concat(args_)) : f(args); }; const uncurry = arity => f => partial(arity)(args => args.reduce((g, x) => g(x), f)); const sum = uncurry(3)(x => y => z => x + y + z); sum(1, 2, 3); // 6 sum(1, 2)(3); // 6 sum(1)(2, 3); // 6 

This tiny arity parameter brings us the desired control. I think it's worth it.

A curry solution for the rest

What do we do with functions that are beyond our control and hence haven't been manually curried?

const curryN = uncurry(2)(arity => f => partial(arity)(args => f(...args))); const add = curryN(2, (x, y) => x + y); const add2 = add(2); add2(4); // 6 

Fortunately, we were able to reuse partial and keep curryN concise. With this solution also variadic functions or such with optional parameters can be curried.

Bonus: "Funcualizing" and currying Methods

To curry methods, we need to transform this nasty, implicit this property in an explicit parameter. It turns out that we can reuse partial for an adequate implementation once again:

const apply = uncurry(2)(arity => key => { return arity ? partial(arity + 1)(args => args[arity][key](...args.slice(0, arity))) : o => o[key](); }); apply(0, "toLowerCase")("A|B|C"); // "a|b|c" apply(0, "toLowerCase", "A|B|C"); // "a|b|c" apply(1, "split")("|")("A|B|C"); // ["A", "B", "C"] apply(1, "split")("|", "A|B|C"); // ["A", "B", "C"] apply(1, "split", "|", "A|B|C"); // ["A", "B", "C"] apply(2, "includes")("A")(0)("A|B|C"); // true apply(2, "includes", "A", 0, "A|B|C"); // true 

In this blog post currying is discussed in detail.

Sign up to request clarification or add additional context in comments.

6 Comments

In this way uncurried functions can only once be applied with an unlimited number of arguments. Any subsequent calls must be made unary. This isn't very idiomatic. You're missing the point. If you're going to write $(sum, 1, 2)(3) or $(sum, 1)(2, 3) then you're defeating the purpose of having $ at all. You might as well just write sum(1)(2)(3). The $ function is in fact idiomatic to Lisp programmers. Instead of $(sum, 1)(2, 3) you would write $($(sum, 1), 2, 3) or even simply $(sum, 1, 2, 3). Your counter examples are trivially unconvincing. Got any serious counter examples?
My $ function is not magic. It's meat to be used consistently. You can't mix $ with normal function application and expect everything to work out. Writing $(sum, 1)(2, 3) is like mixing oil and water. Either use $ or use normal function application but not both. My motivation for writing $ was simplicity and speed. The $ function is simpler than curry and faster than your uncurry implementations. All you got to do is use it consistently and you'll be able to write functional programs in JavaScript using a Lispy syntax with all the benefits that currying has to offer.
@Aadit: When we talk about curried functions in Javascript, then a lot of people associate the following application: f(x, y, z), f(x)(y, z), f(x, y)(z), ... - even if this is no longer the application of curried functions in the mathematical sense. Your own curry implementation does it this way. So it's a completely legitimate question why $ gives up this behavior. Your implementation just doesn't fit my needs and I tend to the bracket syntax.
The OP's question is "Function application for curried functions in JavaScript and ES6". And that's exactly what I'm doing: Utilizing uncurry in order to achieve an application of curried functions that people are used to in JavaScript. Now the question seems to be rather "How to apply curried functions in Lisp style?". So yes, I think I've missed the real question!
True. You do answer the question. +1 for that. In fact, the more I think about it then more sense your answer makes. I think your answer is more deserving of the tick mark, although to be honest I don't agree with the first part of your answer (i.e. that $ is magic).
|
3

Most people write curried functions like this:

var add = curry(function (x, y, z) { return x + y + z; }); add(2, 3, 5); 

Mostly because they don't want to write this:

var add = function (x) { return function (y) { return function (z) { return x + y + z; }; }; }; add(2)(3)(5); 

However, nobody agrees on how to implement curry.

Standards

Then, ECMAScript 6 solved the first problem for us:

var add = x => y => z => x + y + z; 

But, we still have to solve the second problem ourselves:

add(2)(3)(5); 

It's high time that we solve this problem:

var $ = (func, ...args) => args.reduce((f, x) => f(x), func); 

I hope you like Lisp syntax:

$(add, 2, 3, 5); 

Sorry jQuery. Function application is more fundamental.


Also, Bergi's solution is awesome:

const uncurry = func => (...args) => { var result = func; for (let arg of args) result = result(arg); return result; } var add = uncurry(x => y => z => x + y + z); add(2, 3, 5); 

However, I still prefer using $.

7 Comments

Now if you could make me a Haskell-style add $ (2, 3, 5) I'd be happy :-)
arguments in ES6, seriously?
@Bergi Upgraded my solution to ES6.
add(1)(2)(3) isn't a "problem" and ES6 certainly doesn't "solve" or remove the need for currying, it just allows you to write new functions in curried form much more easily. The curry procedure is still necessary for working with existing functions which aren't already curried.
@naomik Perhaps you don't mind writing add(1)(2)(3), which is good for you, but not everybody shares your opinion. You certainly shouldn't force your opinion on others. Also, I was under the impression that you were a proponent of abolishing curry altogether. I guess I was wrong.
|
2

You can easily write a function that applies multiple arguments to such a curried function:

const uncurry = fn => (...args) => args.reduce((f, x) => f(x), fn); // or alternatively: const uncurry = fn => (...args) => { let f = fn; for (const x of args) f = f(x); return f; } 

Now you can invoke add like so:

uncurry(add)(2, 3, 4) 

and if you still hate that you could also use

const $ = uncurry(uncurry); $(add, 2, 3, 4) 

11 Comments

It would be wise to rename apply to uncurry. It works in Haskell too. See the types.
Uh sure it works. But it doesn't pass all the arguments to the inner apply, only a single args, and the rest is done by the outer apply. In Haskell, it becomes (func, (a, b)) instead of (func, a, b), that's what I was confused about
@AaditMShah: Thanks, fixed. When dealing with curried functions, we'd really want to have our parameters to be constant. Hrmpf.
@Bergi great demonstration of a function that utilizes itself re: apply(apply)
@geoffrey If we did const normalAdd = uncurry(curriedAdd), then call normalAdd(1,2,3) and normalAdd(4,5,6), the second call would not work if we had modified fn in the first.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.