带有递归的JS Curry函数

时间:2018-01-17 04:32:18

标签: javascript arrays functional-programming currying

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

我不是要求单一的咖喱电话。

此函数乘以乘法(4,4,4)// 64

function multiplication(...args) {

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

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

同样的功能也应该乘以咖喱函数括号。 e.g。

/*
  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

5 个答案:

答案 0 :(得分:9)

这是一个通用的解决方案,通过重复调用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));

答案 1 :(得分:3)

您的代码

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))




答案 2 :(得分:2)

这是一个类似于4castle的答案,它使用额外的rest参数而不是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

部分申请与currying相关,但不完全相同。我在this answerthis one

中写下了一些差异

答案 3 :(得分:0)

这是一个最小的咖喱功能

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

高级别说明:

我们想构建一个函数,在精神上与Thrush(f => a => f(a))类似,但具有可变输入。我们希望将输入部分地应用于此函数,为第一个参数和其他所需参数传递curried函数f,直到满足或超过f.length给出的函数的适当arity为止

详细说明:

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

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副本我们的原始函数和所有参数(没有参数)
  4. 很酷,所以我们基本上只是现在只回到curry

    来获得相同的功能

    接下来我们称之为

    const inc = curriedAdd(0,1)
    

    现在发生以下情况

    1. 我们调用curried函数。 curriedAdd addthis绑定为第一个参数(null设置为add后)。看起来像这样

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

    2. 这里当我们调用咖喱时,args又是函数的第一个参数。 [0,1]现在是两个args.length >= fn.length的列表。

    3. 因此谓词add.length为false,因为args.length为3,curry为2。
    4. 我们现在绑定到另一个add的新副本,并将其绑定到[0,1],并将两个参数inc传播到bind。
    5. curry.bind(null, add, 0, 1)不是const six = inc(5)

      很酷,现在我们称之为

      inc

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

      因此我们像以前一样打电话给args.length >= fn.length。这一次trueadd,并使用所有三个参数调用args.length >= fn.length

      这个currying函数的一个重要部分是谓词为args.length === fn.length而不是const six = inc(5,undefined) ,否则会失败

      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]
      

      这似乎并不重要,但是,在Javascript中,您可能经常这样做

      reduce

      .elementor-button:focus, .elementor-button:hover, .elementor-button:visited { color: #fff; opacity: .9; } 函数传递一些参数...一个大于我们预期的两个参数(见脚注)。如果我们的curry谓词没有考虑大于场景,那么这段代码就会破坏。

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

      脚注:

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

答案 4 :(得分:0)

一个非常基本的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
&#13;
&#13;
&#13;

但是,当我们检查在函数定义中定义和设置的curry属性时,上述f.length函数对于接受确定数量参数的函数有效。这是一种正常的函数行为,因为纯函数具有固定类型,它与所需的内容和它所提供的内容绑定在一起。但是,JS是松散类型的,而不是纯函数式语言。它可以无限期地获取许多论据。

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

&#13;
&#13;
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
&#13;
&#13;
&#13;