带递归的 JS Curry 函数

JS Curry function with Recursion

请在将其标记为重复之前阅读。

我不是要单咖喱电话。

这个函数乘法,multiplication(4,4,4) //64

function multiplication(...args) {

    return args.reduce((accum, val) => accum * val, 1)
}

但是我正在努力实现其他目标...

这个相同的函数也应该乘以它的 curry 函数括号。 例如

/*
  which return the multiplication of three numbers.
  The function can be called in any of the following forms:

  multiply(2, 3)(4) => 24
  multiply(2)(3, 4) => 24
  multiply(2)(3)(4) => 24
  multiply(2, 3, 4) => 24
*/

请帮忙。

在修改了大量代码并阅读了一些堆栈答案之后。 最后我想出了。但是还是不满足这个multiply(2)(3, 4) => 24

但在其他情况下工作正常

multiply(2,3,4)
multiply(2,3)(4)
multiply(2)(3)(4)

var multiply = function(...args) {
    if (args.length === 3) {
        return args[0] * args[1] * args[2];
    } else {
        return function() {
            args.push([].slice.call(arguments).pop());
            return multiply.apply(this, args);
        };
    }
}

同时 multiply(2)(3, 4) => 24 fail

您的代码

var multiply = function(...args) {
    if (args.length === 3) {
        return args[0] * args[1] * args[2];
    } else {
        return function() { // ***
            args.push([].slice.call(arguments).pop()); // ***
            return multiply.apply(this, args);
        };
    }
}

*** 这两行需要改变,你就快到了,实际上非常接近

var multiply = function(...args) {
    if (args.length === 3) {
        return args[0] * args[1] * args[2];
    } else {
        return function(...args2) { // ***
            args.push(...args2); // ***
            return multiply.apply(this, args);
        };
    }
}
console.log(multiply(2, 3)(4))
console.log(multiply(2)(3, 4))
console.log(multiply(2)(3)(4))
console.log(multiply(2, 3, 4))

ES6 使它更干净

const multiply = (...args) => (args.length === 3) ? args[0] * args[1] * args[2] : (...args2) => multiply(...args.concat(args2));
console.log(multiply(2, 3)(4))
console.log(multiply(2)(3, 4))
console.log(multiply(2)(3)(4))
console.log(multiply(2, 3, 4))

这是一个通用的解决方案,通过重复调用 bind 直到传递了足够的参数。

function curry(func, arity = func.length) {
  return function (...args) {
    if (args.length >= arity) {
      return func(...args);
    } else {
      return curry(func.bind(this, ...args), arity - args.length);
    }
  };
}

const multiply = curry((a, b, c) => a * b * c);

console.log(multiply(2, 3)(4));
console.log(multiply(2)(3, 4));
console.log(multiply(2)(3)(4));
console.log(multiply(2, 3, 4));

这是一个类似于 4castle 的答案,它使用了一个额外的剩余参数而不是 Function.prototype.bind

const curry = (f, ...xs) => (...ys) =>
  f.length > xs.length + ys.length 
    ? curry (f, ...xs, ...ys)
    : f (...xs, ...ys)
    
const multiply =
  curry ((a, b, c) => a * b * c)

console.log (multiply (2, 3) (4))         // 24
console.log (multiply (2) (3, 4))         // 24
console.log (multiply (2) (3) (4))        // 24
console.log (multiply (2, 3, 4))          // 24
console.log (multiply () () () (2, 3, 4)) // 24

但是依赖 length 属性 是一个函数,当可变参数函数发挥作用时可能会出现问题 – 这里,partial 更容易理解,当函数的不会完整提供参数,它适用于可变参数函数。

const multiply = (x, ...xs) =>
  x === undefined
    ? 1
    : x * multiply (...xs)

const partial = (f, ...xs) =>
  (...ys) => f (...xs, ...ys)

console.log (partial (multiply) (2, 3, 4))    // 24
console.log (partial (multiply, 2) (3, 4))    // 24
console.log (partial (multiply, 2, 3) (4))    // 24
console.log (partial (multiply, 2, 3, 4) ())  // 24

console.log (multiply (2, 3, 4, 5, 6, 7))                     // 5040
console.log (partial (multiply, 2, 3, 4) (5, 6, 7))           // 5040
console.log (partial (partial (multiply, 2, 3), 4, 5) (6, 7)) // 5040

部分应用与柯里化有关,但并不完全相同。我在 and this one

中写了一些差异

这是一个最小的 curry 函数

const curry = (fn, ...args) => 
  args.length >= fn.length ? fn(...args) : curry.bind(null, fn, ...args)

高级说明:

我们想要构造一个函数,在本质上类似于 Thrush (f => a => f(a)),但具有可变输入。我们希望将部分输入应用到此函数,为第一个参数传入柯里化函数 f 并传递所需的其余参数,直到满足或超过 f.length 给定的函数的适当元数.

详情:

假设我们有一些添加功能,

const add = (a,b,c) => a+b+c

然后我们咖喱它

const curriedAdd = curry( add )

事情是这样的:

  1. 我们的 curry 函数接收一个函数,没有额外的参数(注意:我们可以在 currying 时传递参数,即 curry( add, 10 )
  2. 谓词args.length >= fn.lengthfalse因为我们没有提供args并且函数的长度是3
  3. 我们将原始函数和所有参数(无参数)绑定到 curry 的新副本

很酷,所以我们基本上只是在绑定到 curry

时才返回相同的函数

接下来我们这样称呼它

const inc = curriedAdd(0,1)

现在发生以下情况

  1. 我们调用柯里化函数。 curriedAddadd 作为第一个参数绑定到它(在 this 设置为 null 之后)。看起来像这样

    const inc = curry.bind(null,add)(0,1)

  2. 这里我们调用curry的时候,add又是函数的第一个参数。 args 现在是两个 [0,1] 的列表。

  3. 谓词 args.length >= fn.length 因此为假,因为 add.length 是 3 而 args.length 是二。
  4. 我们现在绑定到 curry 的另一个新副本,并将其绑定到 add,两个参数 [0,1] 传播到绑定中。

inc 不是 curry.bind(null, add, 0, 1)

太棒了,现在我们称之为

const six = inc(5)

但是 inc 只是 curry.bind(null,add,0,1)

因此我们像以前一样调用了curry。这次 args.length >= fn.lengthtrue 并使用所有三个参数

调用 add

这个柯里化函数的一个重要部分是谓词是 args.length >= fn.length 而不是 args.length === fn.length 因为否则这将失败

const six = inc(5,undefined)

这可能看起来并不重要,但是,在 Javascript 你可能经常会做这样的事情

const concatWith = curry( (fn,a,b) => a.concat(fn(b)) )
const collectObjectValues = concatWith(Object.values)
[ {a: 1, b: 2}, {c: 3} ].reduce( collectObjectValues, [] )
// [1,2,3]

reduce 函数传入了一些参数……一个比我们预期的两个更大的数字(见脚注)。如果我们的 curry 谓词没有考虑大于场景,这段代码就会出错。

希望这是有益的和有教育意义的。享受吧!

脚注:

[1] - 确切地说是四个,参见 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

一个非常基本的 curry 函数可以简单地在 JavaScript 中使用递归实现,如下所示;

var curry = f => f.length ? (...a) => curry(f.bind(f,...a)) : f();

function multiplyDivide (n,m,o,p){
  return n * m / o * p;
}

var curry = f => f.length ? (...a) => curry(f.bind(f,...a)) : f(),
    cmd   = curry(multiplyDivide);

console.log(cmd(4,5,2,10));     // <- 100
console.log(cmd(4)(5,2,10));    // <- 100
console.log(cmd(4,5)(2,10));    // <- 100
console.log(cmd(4,5,2)(10));    // <- 100
console.log(cmd(4)(5)(2,10));   // <- 100
console.log(cmd(4)(5)(2)(10));  // <- 100

然而,上面的 curry 函数对于接受确定数量参数的函数是有效的,因为我们检查在函数定义中定义和设置的 f.length 属性 。这是一个正常的函数行为,因为纯函数有一个固定的类型,绑定了它的取值和给出的值。然而 JS 是松散类型的,而不是纯函数式语言。它可以自由选择无限多的参数。

对于由 rest 运算符指定的无限多个参数,如 (...a)function.length 属性 是 0,强制我们使用 arguments.length 来决定在哪里停止。在这种情况下,柯里化函数每次都会为您提供一个新函数,以便您能够输入新参数,直到您最终不带参数调用它以获得结果。

function prodall(...a){
  return a.reduce((p,c) => p*c);
}

var curry = f => (...a) => a.length ? curry(f.bind(f,...a)) : f(),
    cpa   = curry(prodall);

console.log(cpa(4,5,2,10)());       // <- 400
console.log(cpa(4)(5,2,10)());      // <- 400
console.log(cpa(4,5)(2,10)());      // <- 400
console.log(cpa(4,5,2)(10)());      // <- 400
console.log(cpa(4)(5)(2,10)());     // <- 400
console.log(cpa(4)(5)(2)(10)());    // <- 400
console.log(cpa(4)(5)(2)(10,3)());  // <- 1200