如何在 JavaScript 中正确柯里化一个函数? [英] How to correctly curry a function in JavaScript?

查看:26
本文介绍了如何在 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.lengthn.让 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?

  1. 如果 m === 0 则返回 g.
  2. 如果 m 然后将 f 部分应用于 m 新参数,并返回一个新的柯里化函数,该函数接受剩余的 n - m 参数.
  3. 否则将 f 应用到 m 参数并返回结果.
  1. If m === 0 then just return g.
  2. If m < n then partially apply f to the m new arguments, and return a new curried function which accepts the remaining n - m arguments.
  3. Otherwise apply f to the m 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:

  1. 如果 m === 0 则返回 g.
  2. 如果 m 然后将 f 部分应用于 m 新参数,并返回一个新的柯里化函数,该函数接受剩余的 n - m 参数.
  3. 如果 m === n 然后将 f 应用到 m 参数.如果结果是一个函数,则curry 结果.最后,返回结果.
  4. 如果 m >n 然后将 f 应用到第一个 n 参数.如果结果是一个函数,则curry 结果.最后,将结果应用于剩余的 m - n 参数并返回新结果.
  1. If m === 0 then just return g.
  2. If m < n then partially apply f to the m new arguments, and return a new curried function which accepts the remaining n - m arguments.
  3. If m === n then apply f to the m arguments. If the result is a function then curry the result. Finally, return the result.
  4. If m > n then apply f to the first n arguments. If the result is a function then curry the result. Finally, apply the result to the remaining m - 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:

这个扩展的 `compose` 函数的好名字是什么?

你可以简单地写:

const project = compose(map, pick);

正如问题中提到的,如果你想组合 lengthfilter 那么你使用 (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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆