JavaScript和ES6中用于咖喱函数的函数应用程序

Aad*_*hah 1 javascript functional-programming currying apply ecmascript-6

我喜欢ECMAScript 6允许您编写如下的咖喱函数:

var add = x => y => z => x + y + z;
Run Code Online (Sandbox Code Playgroud)

但是,我讨厌我们需要在咖喱函数的每个参数中加上括号:

add(2)(3)(5);
Run Code Online (Sandbox Code Playgroud)

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

add(2, 3, 5);
Run Code Online (Sandbox Code Playgroud)

我该怎么办?我不在乎性能。

bob*_*bob 5

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


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

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

  • 咖喱函数称为一元函数序列: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
Run Code Online (Sandbox Code Playgroud)

通过这种方式,未经处理的函数只能一次使用无限数量的参数。任何后续呼叫都必须设为一元。该功能完全符合其承诺。但是,它不允许应用咖喱函数,例如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
Run Code Online (Sandbox Code Playgroud)

如果您喜欢自动取消循环,则此实现有效:一旦未管理的函数本身生成了一个已管理的函数作为返回值,则此已返回的函数将自动被取消管理。如果您希望获得更多控制,则以下实现可能更合适。

最终的非即时实现

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
Run Code Online (Sandbox Code Playgroud)

这个很小的参数会为我们带来所需的控制。我认为这是值得的。

其余的咖喱解决方案

我们如何处理超出我们控制范围的功能,因此没有进行手动处理?

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
Run Code Online (Sandbox Code Playgroud)

幸运的是,我们能够重复使用partial并保持curryN简洁。通过该解决方案,还可以处理可变参数函数或具有可选参数的函数。

奖励:“功能化”和欺骗性方法

要使用方法,我们需要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
Run Code Online (Sandbox Code Playgroud)

在此博客文章中,详细讨论了currying。