JavaScript和ES6中curried函数的函数应用程序

时间:2016-02-03 01:54:50

标签: javascript functional-programming ecmascript-6 apply currying

我喜欢ECMAScript 6允许你编写像这样的curried函数:

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

但是,我讨厌我们需要将curried函数的每个参数括起来:

add(2)(3)(5);

我希望能够一次将curried函数应用于多个参数:

add(2, 3, 5);

我该怎么办?我不关心表现。

4 个答案:

答案 0 :(得分:4)

Currying和curried函数的应用是Javascript中有争议的问题。简单来说,有两种相反的观点,我简要说明了这两种观点。

- 仅在必要时使用单独的咖喱功能

对其他语言或范例的概念进行调整原则上是一件好事。但是,这种适应应该用目标语言的基本手段来完成。这对javascript中的currying意味着什么?

  • curried函数被称为一系列一元函数:add3(1)(2)(3); // 6
  • 使用箭头const add3 = x => y => z => x + y + z;
  • 手动调整自己的功能
  • 第三方功能或方法由单独的咖喱功能构成

- 默认使用单独的咖喱实施

提议的$ / 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

通过这种方式,未经证实的函数只能使用无限数量的参数。任何后续调用都必须是一元的。该功能完全符合它的承诺。但是,它不允许应用curried函数,例如JavaScript开发人员习惯。目前大多数咖喱实施都更灵活。这是一个扩展实现:

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

如果您喜欢自动发布,则此实现有效:一旦未处理的函数本身产生一个curried函数作为返回值,此返回的函数将自动解除。如果您更喜欢更多控制,则以下实现可能更合适。

最终的不良实施

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

这个微小的arity参数为我们带来了理想的控制。我认为这是值得的。

其余的咖喱解决方案

我们如何处理超出我们控制范围的功能,因此手动咖喱?

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

幸运的是,我们能够重用partial并保持curryN简洁。使用此解决方案,还可以使用可选参数的可变参数函数。

奖金:&#34; Funcualizing&#34;和currying方法

要理解方法,我们需要在显式参数中转换这个讨厌的隐式this属性。事实证明,我们可以再次使用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

在此blog post讨论中将详细讨论。

答案 1 :(得分:3)

大多数人都会写这样的curried函数:

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

add(2, 3, 5);

主要是因为他们不想写这个:

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

add(2)(3)(5);

但是,nobody agrees on how to implement curry

Standards

然后,ECMAScript 6为我们解决了第一个问题:

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

但是,我们仍然必须自己解决第二个问题:

add(2)(3)(5);

现在是我们解决这个问题的最佳时机:

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

我希望你喜欢Lisp语法:

$(add, 2, 3, 5);

抱歉jQuery。功能应用更为基础。

此外,Bergi的solution非常棒:

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);

但是,我仍然更喜欢使用$

答案 2 :(得分:2)

您可以轻松编写一个将多个参数应用于这种curried函数的函数:

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

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

现在您可以像这样调用add

apply(add)(2, 3, 4)

如果你仍然讨厌你也可以使用

const $ = apply(apply);

$(add, 2, 3, 4)

答案 3 :(得分:0)

鉴于Bergi的这个好答案

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

并不是很明显它和

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

我只是提供这个答案作为见解。一旦拥有apply

的通用名称,reduce就会进一步简化
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);

我认为能够看到以这种简化形式编写的apply是很有价值的,即使这最终不是最终的实现。

在看到Bergi的优雅$之后受到启发,我会对我的实施做出最后的改变

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

现在它就像这样使用

// 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
  

我不关心表现。

好的!这个实现读得非常好但它确实利用了更多的函数调用,所以它执行得更慢。