JavaScript 和 ES6 中柯里化函数的函数应用
Function application for curried functions in JavaScript and ES6
我喜欢 ECMAScript 6 允许您像这样编写柯里化函数:
var add = x => y => z => x + y + z;
但是,我讨厌我们需要用括号括起柯里化函数的每个参数:
add(2)(3)(5);
我希望能够同时将柯里化函数应用于多个参数:
add(2, 3, 5);
我该怎么办?我不关心性能。
大多数人这样写柯里化函数:
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 .
然后,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 的 很棒:
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);
不过,我还是比较喜欢用$
。
您可以轻松地编写一个函数,将多个参数应用于这样一个柯里化函数:
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;
}
现在您可以像这样调用 add
:
uncurry(add)(2, 3, 4)
如果您仍然讨厌它,您也可以使用
const $ = uncurry(uncurry);
$(add, 2, 3, 4)
柯里化和柯里化函数的应用在 Javascript 中是有争议的问题。简单来说,有两种相反的观点,我简单地说明一下。
- 仅在必要时使用单独的 curry 函数
从其他语言或范例中改编概念原则上是一件好事。但是,这种改编应该使用目标语言的基本方法来完成。这对 javascript 中的柯里化意味着什么?
- 柯里化函数被称为一元函数序列:
add3(1)(2)(3); // 6
- 自己的函数用箭头手动柯里化
const add3 = x => y => z => x + y + z;
- 第三方函数或方法由单独的咖喱函数咖喱
- 默认使用单独的 curry 实现
建议的 $
/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 开发人员习惯的。大多数当前的 curry 实现都更加灵活。这是一个扩展的实现:
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
如果你喜欢自动取消柯里化,这个实现是可行的:一旦一个取消柯里化的函数本身产生一个柯里化函数作为 return 值,这个 returned 函数就会自动取消柯里化。如果你更喜欢更多的控制,下面的实现可能更合适。
最后的 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
这个微小的参数给我们带来了想要的控制。我觉得值得。
剩下的咖喱解决方案
我们如何处理超出我们控制范围且未被手动柯里化的函数?
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
简洁。使用此解决方案,还可以柯里化带有可选参数的可变函数等。
奖励:"Funcualizing" 和柯里化方法
为了柯里化方法,我们需要将这个令人讨厌的隐式 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 中详细讨论了柯里化。
我喜欢 ECMAScript 6 允许您像这样编写柯里化函数:
var add = x => y => z => x + y + z;
但是,我讨厌我们需要用括号括起柯里化函数的每个参数:
add(2)(3)(5);
我希望能够同时将柯里化函数应用于多个参数:
add(2, 3, 5);
我该怎么办?我不关心性能。
大多数人这样写柯里化函数:
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
然后,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 的
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);
不过,我还是比较喜欢用$
。
您可以轻松地编写一个函数,将多个参数应用于这样一个柯里化函数:
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;
}
现在您可以像这样调用 add
:
uncurry(add)(2, 3, 4)
如果您仍然讨厌它,您也可以使用
const $ = uncurry(uncurry);
$(add, 2, 3, 4)
柯里化和柯里化函数的应用在 Javascript 中是有争议的问题。简单来说,有两种相反的观点,我简单地说明一下。
- 仅在必要时使用单独的 curry 函数
从其他语言或范例中改编概念原则上是一件好事。但是,这种改编应该使用目标语言的基本方法来完成。这对 javascript 中的柯里化意味着什么?
- 柯里化函数被称为一元函数序列:
add3(1)(2)(3); // 6
- 自己的函数用箭头手动柯里化
const add3 = x => y => z => x + y + z;
- 第三方函数或方法由单独的咖喱函数咖喱
- 默认使用单独的 curry 实现
建议的 $
/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 开发人员习惯的。大多数当前的 curry 实现都更加灵活。这是一个扩展的实现:
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
如果你喜欢自动取消柯里化,这个实现是可行的:一旦一个取消柯里化的函数本身产生一个柯里化函数作为 return 值,这个 returned 函数就会自动取消柯里化。如果你更喜欢更多的控制,下面的实现可能更合适。
最后的 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
这个微小的参数给我们带来了想要的控制。我觉得值得。
剩下的咖喱解决方案
我们如何处理超出我们控制范围且未被手动柯里化的函数?
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
简洁。使用此解决方案,还可以柯里化带有可选参数的可变函数等。
奖励:"Funcualizing" 和柯里化方法
为了柯里化方法,我们需要将这个令人讨厌的隐式 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 中详细讨论了柯里化。