如何在 JavaScript 中正确柯里化一个函数? [英] How to correctly curry a function in JavaScript?
问题描述
我用 JavaScript 编写了一个简单的 curry
函数,它在大多数情况下都能正常工作:
I wrote a simple curry
function in JavaScript which works correctly for most cases:
const add = curry((a, b, c) => a + b + c);
const add2 = add(2);
const add5 = add2(3);
console.log(add5(5));
<script>
const curried = Symbol("curried");
Object.defineProperty(curry, curried, { value: true });
function curry(functor, ...initArgs) {
if (arguments.length === 0) return curry;
if (typeof functor !== "function") {
const value = JSON.stringify(functor);
throw new TypeError(`${value} is not a function`);
}
if (functor[curried] || initArgs.length >= functor.length)
return functor(...initArgs);
const result = (...restArgs) => curry(functor, ...initArgs, ...restArgs);
return Object.defineProperty(result, curried, { value: true });
}
</script>
但是,它不适用于以下情况:
However, it doesn't work for the following case:
// length :: [a] -> Number
const length = a => a.length;
// filter :: (a -> Bool) -> [a] -> [a]
const filter = curry((f, a) => a.filter(f));
// compose :: (b -> c) -> (a -> b) -> a -> c
const compose = curry((f, g, x) => f(g(x)));
// countWhere :: (a -> Bool) -> [a] -> Number
const countWhere = compose(compose(length), filter);
根据以下问题 countWhere
被定义为 (length .) .过滤器
:
According to the following question countWhere
is defined as (length .) . filter
:
有什么作用 (f .) .g 在 Haskell 中是什么意思?
因此我应该能够使用 countWhere
如下:
Hence I should be able to use countWhere
as follows:
const odd = n => n % 2 === 1;
countWhere(odd, [1,2,3,4,5]);
然而,它返回一个函数,而不是返回3
(数组的长度[1,3,5]
).我做错了什么?
However, instead of returning 3
(the length of the array [1,3,5]
), it returns a function. What am I doing wrong?
推荐答案
curry
函数的问题(对于 大多数 curry
函数 那个 人们 write 在 JavaScript 中)是它不处理正确的额外参数.
The problem with your curry
function (and for most curry
functions that people write in JavaScript) is that it doesn't handle extra arguments correctly.
curry
的作用
What curry
does
假设f
是一个函数,f.length
是n
.让 curry(f)
为 g
.我们用 m
参数调用 g
.应该怎么办?
Suppose f
is a function and f.length
is n
. Let curry(f)
be g
. We call g
with m
arguments. What should happen?
- 如果
m === 0
则返回g
. - 如果
m
然后将 f
部分应用于m
新参数,并返回一个新的柯里化函数,该函数接受剩余的n - m
参数. - 否则将
f
应用到m
参数并返回结果.
- If
m === 0
then just returng
. - If
m < n
then partially applyf
to them
new arguments, and return a new curried function which accepts the remainingn - m
arguments. - Otherwise apply
f
to them
arguments and return the result.
这是大多数curry
函数所做的,这是错误的.前两种情况是对的,但第三种情况是错误的.相反,它应该是:
This is what most curry
functions do, and this is wrong. The first two cases are right, but the third case is wrong. Instead, it should be:
- 如果
m === 0
则返回g
. - 如果
m
然后将 f
部分应用于m
新参数,并返回一个新的柯里化函数,该函数接受剩余的n - m
参数. - 如果
m === n
然后将f
应用到m
参数.如果结果是一个函数,则curry
结果.最后,返回结果. - 如果
m >n
然后将f
应用到第一个n
参数.如果结果是一个函数,则curry
结果.最后,将结果应用于剩余的m - n
参数并返回新结果.
- If
m === 0
then just returng
. - If
m < n
then partially applyf
to them
new arguments, and return a new curried function which accepts the remainingn - m
arguments. - If
m === n
then applyf
to them
arguments. If the result is a function thencurry
the result. Finally, return the result. - If
m > n
then applyf
to the firstn
arguments. If the result is a function thencurry
the result. Finally, apply the result to the remainingm - n
arguments and return the new result.
大多数curry
函数的问题
The problem with most curry
functions
考虑以下代码:
const countWhere = compose(compose(length), filter);
countWhere(odd, [1,2,3,4,5]);
如果我们使用了错误的curry
函数,那么这相当于:
If we use the incorrect curry
functions, then this is equivalent to:
compose(compose(length), filter, odd, [1,2,3,4,5]);
然而,compose
只接受三个参数.最后一个参数被删除:
However, compose
only accepts three arguments. The last argument is dropped:
const compose = curry((f, g, x) =>f(g(x)));
因此,上述表达式的计算结果为:
Hence, the above expression evaluates to:
compose(length)(filter(odd));
这进一步评估为:
compose(length, filter(odd));
compose
函数需要一个更多的参数,这就是为什么它返回一个函数而不是返回 3
.要获得正确的输出,您需要编写:
The compose
function expects one more argument which is why it returns a function instead of returning 3
. To get the correct output you need to write:
countWhere(odd)([1,2,3,4,5]);
这就是大多数curry
函数出错的原因.
This is the reason why most curry
functions are wrong.
使用正确curry
函数的解决方案
The solution using the correct curry
function
再次考虑以下代码:
const countWhere = compose(compose(length), filter);
countWhere(odd, [1,2,3,4,5]);
如果我们使用正确的curry
函数,那么这相当于:
If we use the correct curry
function, then this is equivalent to:
compose(compose(length), filter, odd)([1,2,3,4,5]);
计算结果为:
compose(length)(filter(odd))([1,2,3,4,5]);
进一步评估为(跳过中间步骤):
Which further evaluates to (skipping an intermediate step):
compose(length, filter(odd), [1,2,3,4,5]);
结果:
length(filter(odd, [1,2,3,4,5]));
产生正确的结果3
.
正确的curry
函数的实现
The implementation of the correct curry
function
在 ES6 中实现正确的 curry
函数很简单:
Implementing the correct curry
function in ES6 is straightforward:
const curried = Symbol("curried");
Object.defineProperty(curry, curried, { value: true });
function curry(functor, ...initArgs) {
if (arguments.length === 0) return curry;
if (typeof functor !== "function") {
const value = JSON.stringify(functor);
throw new TypeError(`${value} is not a function`);
}
if (functor[curried]) return functor(...initArgs);
const arity = functor.length;
const args = initArgs.length;
if (args >= arity) {
const result = functor(...initArgs.slice(0, arity));
return typeof result === "function" || args > arity ?
curry(result, ...initArgs.slice(arity)) : result;
}
const result = (...restArgs) => curry(functor, ...initArgs, ...restArgs);
return Object.defineProperty(result, curried, { value: true });
}
我不确定这个 curry
的实现有多快.也许有人可以让它更快.
I am not sure how fast this implementation of curry
is. Perhaps somebody could make it faster.
使用正确curry
函数的影响
Implications of using the correct curry
function
使用正确的curry
函数可以让您直接将 Haskell 代码翻译成 JavaScript.例如:
Using the correct curry
function allows you to directly translate Haskell code into JavaScript. For example:
const id = curry(a => a);
const flip = curry((f, x, y) => f(y, x));
id
函数很有用,因为它允许您轻松地部分应用非柯里化函数:
The id
function is useful because it allows you to partially apply a non-curried function easily:
const add = (a, b) => a + b;
const add2 = id(add, 2);
flip
函数很有用,因为它允许您在 JavaScript 中轻松创建正确的部分:
The flip
function is useful because it allows you to easily create right sections in JavaScript:
const sub = (a, b) => a - b;
const sub2 = flip(sub, 2); // equivalent to (x - 2)
这也意味着你不需要像这样的黑客扩展compose代码>函数
:
It also means that you don't need hacks like this extended compose
function:
你可以简单地写:
const project = compose(map, pick);
正如问题中提到的,如果你想组合 length
和 filter
那么你使用 (f .) .g
模式:
As mentioned in the question, if you want to compose length
and filter
then you use the (f .) . g
pattern:
有什么作用 (f .) .g 在 Haskell 中是什么意思?
另一种解决方案是创建更高阶的compose
函数:
Another solution is to create higher order compose
functions:
const compose2 = compose(compose, compose);
const countWhere = compose2(length, fitler);
由于curry
函数的正确实现,这一切都是可能的.
This is all possible because of the correct implementation of the curry
function.
额外的思考
当我想组合一个函数链时,我通常使用下面的chain
函数:
I usually use the following chain
function when I want to compose a chain of functions:
const chain = compose((a, x) => {
var length = a.length;
while (length > 0) x = a[--length](x);
return x;
});
这允许您编写如下代码:
This allows you to write code like:
const inc = add(1);
const foo = chain([map(inc), filter(odd), take(5)]);
foo([1,2,3,4,5,6,7,8,9,10]); // [2,4,6]
相当于下面的 Haskell 代码:
Which is equivalent to the following Haskell code:
let foo = map (+1) . filter odd . take 5
foo [1,2,3,4,5,6,7,8,9,10]
它还允许您编写如下代码:
It also allows you to write code like:
chain([map(inc), filter(odd), take(5)], [1,2,3,4,5,6,7,8,9,10]); // [2,4,6]
相当于下面的 Haskell 代码:
Which is equivalent to the following Haskell code:
map (+1) . filter odd . take 5 $ [1,2,3,4,5,6,7,8,9,10]
希望有所帮助.
这篇关于如何在 JavaScript 中正确柯里化一个函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!