Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JS Curry function with Recursion

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

like image 466
STEEL Avatar asked Jan 17 '18 04:01

STEEL


4 Answers

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));
like image 64
4castle Avatar answered Nov 01 '22 07:11

4castle


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))
like image 20
Jaromanda X Avatar answered Nov 01 '22 07:11

Jaromanda X


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

like image 2
Mulan Avatar answered Nov 01 '22 06:11

Mulan


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

like image 1
Babakness Avatar answered Nov 01 '22 08:11

Babakness