Function application for curried functions in JavaScript and ES6

╄→гoц情女王★ 提交于 2021-02-05 08:35:24

问题


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.


回答1:


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.




回答2:


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.

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 $.




回答3:


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)


来源:https://stackoverflow.com/questions/35167153/function-application-for-curried-functions-in-javascript-and-es6

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!