7

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

8
  • 3
    Possible duplicate of Currying a function that takes infinite arguments Commented Jan 17, 2018 at 4:41
  • @Herohtar kindly mutliply(2,3)(4) and test Commented Jan 17, 2018 at 4:43
  • Sorry your self answer was wrong, doesn't mean I know an answer Commented Jan 17, 2018 at 4:48
  • 2
    you ASSume I downvoted, but you deleted the answer before I even thought about it - perhaps the downvotes were because your answer was wrong Commented Jan 17, 2018 at 4:55
  • 1
    your code was 3/4ths the way there, you just have to re-think it I guess Commented Jan 17, 2018 at 5:00

5 Answers 5

20

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

Sign up to request clarification or add additional context in comments.

1 Comment

damm thats a perfect FP answer :) love it
3

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 Comments

Thanks for sticking with me till the end. Your answer is really good. But I choose @4castle answer
@4castle answer is definitely better - I just wanted to point out how close your code was
:) glad to hear
2

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

1 Comment

fwiw, currying has a strict definition that does not match what you intend for curry to do. You might want to call your solution autoCurry or something.
1

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

Comments

1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.