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.

Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299

3 Answers3

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.

  • _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? – Aadit M Shah Mar 12 '16 at 18:10
  • 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 M Shah Mar 12 '16 at 18:20
  • @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](http://stackoverflow.com/questions/27996544/how-to-correctly-curry-a-function-in-javascript) 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. –  Mar 12 '16 at 19:47
  • 1
    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! –  Mar 13 '16 at 10:08
  • 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). – Aadit M Shah Mar 13 '16 at 15:40
  • @Aadit I updated my answer to better cover the complex discussion. Thanks! –  Mar 13 '16 at 18:31
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 $.

Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 1
    Now if you could make me a Haskell-style `add $ (2, 3, 5)` I'd be happy :-) – Bergi Feb 03 '16 at 01:59
  • `arguments` in ES6, seriously? – Bergi Feb 03 '16 at 02:00
  • @Bergi Upgraded my solution to ES6. – Aadit M Shah Feb 03 '16 at 02:04
  • 1
    `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. – Mulan Mar 12 '16 at 14:39
  • @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. – Aadit M Shah Mar 12 '16 at 16:51
  • @AaditMShah I see no problem with Bergi's solution, but I'm curious why it was copy/pasted into your answer. Why not just delete this and accept his? – Mulan Mar 12 '16 at 19:10
  • @naomik I incorporated his answer into mine because I acknowledged that his solution is good and I wanted it to gain more recognition. I didn't pass off his answer as mine. I gave him credit where credit is due. However, I still think my answer is better because an uncurried function can only be applied once (after which it needs to be uncurried again it you want to apply it once more) whereas `$` can be used consistently. That's why I think my answer is more deserving of the tick mark. If you can provide a better answer then I'll be happy to accept your answer. – Aadit M Shah Mar 12 '16 at 19:19
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)
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I like where you're going with this. – Aadit M Shah Feb 03 '16 at 02:11
  • 1
    It would be wise to rename `apply` to `uncurry`. It works in Haskell too. See the types. – Aadit M Shah Feb 03 '16 at 02:15
  • 1
    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 – Bergi Feb 03 '16 at 02:19
  • @AaditMShah: I thought of the JS `apply` method which takes an array of arbitrary length, instead of a two-tupel only. `uncurry` surely would work as well – Bergi Feb 03 '16 at 02:23
  • I see what you mean. Well, it doesn't work as expected but it still produces the same result. – Aadit M Shah Feb 03 '16 at 02:23
  • I just realized that in your alternative version of `apply` you're modifying the variable `f` in the parent function. – Aadit M Shah Feb 03 '16 at 02:37
  • 1
    @AaditMShah: Thanks, fixed. When dealing with curried functions, we'd really want to have our [parameters to be `const`ant](http://stackoverflow.com/a/31745585/1048572). Hrmpf. – Bergi Feb 03 '16 at 02:44
  • 2
    @Bergi great demonstration of a function that utilizes itself re: `apply(apply)` – Mulan Mar 12 '16 at 14:45
  • Would you mind stating why it is important not to reassign the parameter fn in the apply function and how dealing with curried functions influences this matter? I fail to see what could go wrong. – geoffrey Apr 25 '20 at 01:30
  • 1
    @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. – Bergi Apr 25 '20 at 10:45
  • Oh my... If you define it the other way round, the problem doesn't present itself because there is a closure `apply = f => (...xs) => $(f, ...xs)` with `$ = (f, ...xs) => { for (const x of xs) f = f(x); return f; }`. Getting used to curried functions is a process. Thanks. – geoffrey Apr 25 '20 at 15:56