2

I came across this interesting problem. Write a javascript function that returns sum of all the arguments passed to it, through multiple calls to that same function.

Here are the ways function can be called -

sum(1, 2, 3, 4);
sum(1, 2)(3, 4);
sum(1, 2)(3)(4);
sum(1, 2, 3)(4);
sum(1)(2, 3, 4);

All the calls above should work and return 10.

Here's what I have written so far, but it only works for first two function calls sum(1, 2, 3, 4) and sum(1, 2)(3, 4) and shits the bed for rest of it.

const arr = [];

function sum(...args) {
  if (args.length === 4) {
    return args.reduce((acc, curr) => {
      return (acc = acc + curr);
    }, 0);
  } else {
    arr.push(...args);
    return function(...args) {
      arr.push(...args);
      return sum(...arr);
    };
  }
}

Someone please help me, this is driving me nuts.

Thanks!

PowPowPow
  • 306
  • 1
  • 2
  • 10
  • 1
    Allowing an arbitrary number of calls will be problematic because to make the chaining work, you need to return a function. But at some point you need to return a number with the final result. – Mark Aug 22 '18 at 01:30
  • This can only work with a specific number of arguments (in this case, 4). – We Are All Monica Aug 22 '18 at 01:36

2 Answers2

6

You're pretty close. This is a perfect opportunity to use the .bind method to return a function that captures the first arguments if you haven't yet been given at least four.

So something like:

function sum(...args) {
  if (args.length >= 4) {
    return args.reduce((acc, curr) => {
      return (acc = acc + curr);
    }, 0);
  } else {
    // Bind the arguments you have to this function, and return it:
    return sum.bind(null, ...args)
  }
}

console.log(sum(1, 2, 3, 4));
console.log(sum(1, 2)(3, 4));
console.log(sum(1, 2)(3)(4));
console.log(sum(1, 2, 3)(4));
console.log(sum(1)(2, 3, 4));

Finally, I'd change the condition to check for >= 4 so that passing more than that doesn't result in a case where you'll curry forever.

CRice
  • 29,968
  • 4
  • 57
  • 70
  • That's right, or more accurately, it needs *at least four*. – CRice Aug 22 '18 at 01:54
  • Awesome! Thanks a lot! Quick question - how does this code retain arguments from previous call? Meaning, in case of sum(1,2)(3,4), how is the second function call getting arguments from the first function call? Apologies if this is a dumb question. – PowPowPow Aug 22 '18 at 01:55
  • Sort of 'at least four', for example this is en error: `sum(1, 2)(3, 4)(5, 6)` – Mark Aug 22 '18 at 02:00
  • 1
    @PowPowPow That's where the `.bind` method comes in. Using `.bind` returns a new function, that when called, automatically precedes any arguments given to the new function with those you've passed to `bind`. [Here are the docs for `bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind) which explain it a little better. – CRice Aug 22 '18 at 02:02
1

Currying has a specific and defined behavior that doesn't mix well with variadic functions due to the undefined arity. However in your particular problem you specify an arity (for example, 4) so its possible to know when to return a result

const curryN = (n, f, ...xs) =>
  (...ys) =>
    ys.length >= n
      ? f (...xs, ...ys)
      : curryN (n - ys.length, f, ...xs, ...ys)
      
const add = (...numbers) =>
  numbers.reduce ((a, b) => a + b, 0)
  
const curryAdd =
  curryN (4, add)
  
console.log
  ( curryAdd (1) (2) (3) (4) // 10
  , curryAdd (1, 2) (3, 4)   // 10
  , curryAdd (1, 2, 3) (4)   // 10
  , curryAdd (1) (2, 3, 4)   // 10
  , curryAdd (1, 2, 3, 4)    // 10
  )

This is a fragile way to design your program though, and it's not even true currying, which only accepts 1 argument per application. Partial application is better because it produces a program with a much more reliable behavior

const partial = (f, ...xs) =>
  (...ys) =>
    f (...xs, ...ys)
    
const add = (...numbers) =>
  numbers.reduce ((a, b) => a + b, 0)
  
console.log
  ( partial (add, 1) (2, 3, 4) // 10
  , partial (add, 1, 2) (3, 4) // 10
  , partial (add, 1, 2, 3) (4) // 10
  )

Please read this related answer for additional insight.

Mulan
  • 129,518
  • 31
  • 228
  • 259