JS Curry function with Recursion

2019-02-07 17:29发布

问题:

Kindly read before you mark it as duplicate.

Im not asking for single curry call.

This functions multiplies, multiplication(4,4,4) //64

function multiplication(...args) {

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

But Im trying to achieve something else...

This same function should multiply its curry function parenthesis as well. 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
*/

Kindly help.

After fiddling through a lot of code and reading some stack answers. Finally I came up with. But it still doesnt satisfy this multiply(2)(3, 4) => 24

But works fine for rest of the cases

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

while multiply(2)(3, 4) => 24 fail

回答1:

Here's a generalized solution that works by repeatedly calling bind until enough parameters have been passed.

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



回答2:

Your code

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

*** these two lines needed changing, you were almost there, so tantalisingly close in fact

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 makes it even cleaner

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



回答3:

Here's an answer similar to 4castle's that uses an additional rest parameter instead of 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

But relying upon the length property is a function can be a problem when variadic functions come into play – Here, partial is easier to understand, explicitly communicates when a function's arguments will not be supplied in entirety, and it works with variadic functions.

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

Partial application is related to currying, but not exactly the same thing. I write about some of the differences in this answer and this one



回答4:

Here is a minimal curry function

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

High Level Explanation:

We want to construct a function, similar in spirit to Thrush ( f => a => f(a) ) but with variadic inputs. We want to partially apply input to this function, passing in the curried function f for the first parameter and the rest of the parameters needed until the appropriate arity for our function, given by f.length is met or exceeded.

Details:

Suppose we have some add function,

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

and we curry it

const curriedAdd = curry( add )

Here is what happens:

  1. Our curry function receives a function, with no additional arguments (note: we could have passed parameters at the time of the currying, ie curry( add, 10 ) )
  2. The predicate args.length >= fn.length is false because we provided no args and the function has a length of 3
  3. We bind to a new copy of curry our original function and all of our arguments (no arguments)

Cool so we basically just get the same function back only now its bound to curry

Next we call it thus

const inc = curriedAdd(0,1)

Now the following happens

  1. We invoke the curried function. curriedAdd is add bound to it as the first parameter (after this is set to null). It looks like this

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

  2. Here when we invoke curry, add is again the first parameter of the function. args is now a list of two [0,1].

  3. The predicate args.length >= fn.length is therefore false because add.length is 3 and args.length is two.
  4. We now bind to yet another fresh copy of curry and bind that to add with two parameters [0,1] spread into bind.

inc is not curry.bind(null, add, 0, 1)

Cool, so now we call this

const six = inc(5)

But inc is just curry.bind(null,add,0,1)

Thus we called curry as before. This time args.length >= fn.length is true and invoke add with all three parameters

An important part of this currying function is that the predicate be args.length >= fn.length and not args.length === fn.length because otherwise this would fail

const six = inc(5,undefined)

This may seem not important, however, in Javascript you might often do something like this

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]

The reduce function passes in a few parameters... a number greater than our expected two (see footnotes). If our curry predicate didn't account for the greater than scenario, this code would break.

Hope this was informative and educational. Enjoy!

Footnotes:

[1] - four to be exact, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce



回答5:

A very basical curry function can simply be implemented in JavaScript by using recursion as follows;

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

However the above curry function is valid for functions which take definite number of arguments as we check the f.length property which is defined and set at the function's definition. This is a normal functional behaviour since pure functions have a solid type bound with what it takes and what it gives. However JS is loosely typed, and not a pure functional language. It has the freedom of taking indefinitely many arguments.

For indefinitely many arguments designated by the rest operator like (...a), the function.length property is 0, enforcing us to use the arguments.length to decide where to stop. In this case the curried function will give you a new function every single time for you to be able to enter new arguments up until you invoke it with no arguments finally to get the result.

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