简体   繁体   English

JavaScript 和 ES6 中柯里化函数的函数应用

[英]Function application for curried functions in JavaScript and ES6

I love that ECMAScript 6 allows you to write curried functions like this:我喜欢 ECMAScript 6 允许您编写这样的柯里化函数:

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.我不在乎性能。

Currying and the application of curried functions are controversial issues in Javascript.柯里化和柯里化函数的应用在 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?这对 javascript 中的柯里化意味着什么?

  • curried functions are called as a sequence of unary functions: add3(1)(2)(3); // 6柯里化函数被称为一元函数序列: add3(1)(2)(3); // 6 add3(1)(2)(3); // 6
  • own functions are manually curried with arrows const add3 = x => y => z => x + y + z;自己的函数是用箭头手动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:建议的$ / uncurry函数存在问题:

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.但是,它不允许应用柯里化函数,如 JavaScript 开发人员所习惯的。 Most of the current curry implementations are more flexible.大多数当前的 curry 实现都更加灵活。 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最后的 uncurry 实现

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.这个微小的 arity 参数为我们带来了所需的控制。 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.幸运的是,我们能够重用partial并保持curryN简洁。 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.为了柯里化方法,我们需要在显式参数中转换这个讨厌的、隐式的this属性。 It turns out that we can reuse partial for an adequate implementation once again:事实证明,我们可以再次重用partial来实现适当的实现:

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.在这篇博文中详细讨论了柯里化。

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 .然而,没有人同意怎么实现curry

标准

Then, ECMAScript 6 solved the first problem for us:然后,ECMAScript 6 为我们解决了第一个问题:

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:我希望你喜欢 Lisp 语法:

$(add, 2, 3, 5);

Sorry jQuery.对不起 jQuery。 Function application is more fundamental.函数应用更基础。


Also, Bergi's solution is awesome:此外,Bergi 的解决方案也很棒:

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 $ .但是,我仍然更喜欢使用$

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:现在你可以像这样调用add

uncurry(add)(2, 3, 4)

and if you still hate that you could also use如果你仍然讨厌它,你也可以使用

const $ = uncurry(uncurry);

$(add, 2, 3, 4)

Given this good answer from Bergi 鉴于Bergi的良好回答

const apply = fn => (...args) => args.reduce((f, x) => f(x), fn);

It's not immediately apparent that it's the same as 现在还不清楚是否与

const apply = fn => (...args) => args.reduce(uncurry(id), fn);

I provide this answer only as an insight. 我提供此答案仅供参考。 apply is even further simplified once you have a generic for reduce 一旦有了通用的reduce apply就可以进一步简化

const id = x => x;
const uncurry = f => (x,y) => f(x)(y);
const reduce = f => y => xs => xs.reduce(uncurry(f), y);

// fresh !
const apply = f => (...xs) => reduce(id)(f)(xs);

I think it's valuable to be able to see apply written in this simplified form, even if this doesn't end up being your final implementation. 我认为能够以简化形式看到apply是很有价值的,即使这最终不是您的最终实现。

Inspired after seeing Bergi's elegant $ , I would make one final change to my implementation 看到Bergi优雅的$启发,我将对实现进行最后更改

const apply = reduce(id);
const $ = (f, ...xs) => apply(f)(xs);

Now it's used like this 现在这样使用

// given
let add = x => y => z => x + y + z;

// with an existing list of inputs
let nums = [1,2,3];
apply(add)(nums); // 6

// arbitrary inputs
$(add, 1, 2, 3); // 6

I don't care about performance. 我不在乎性能。

Well good ! 很好! This implementation does read very nicely but it does utilize more function calls, so it will perform slower. 该实现的读取效果很好,但是它确实利用了更多的函数调用,因此执行起来更慢。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM