51

I need a js sum function to work like this:

sum(1)(2) = 3
sum(1)(2)(3) = 6
sum(1)(2)(3)(4) = 10 
etc.

I heard it can't be done. But heard that if adding + in front of sum can be done. Like +sum(1)(2)(3)(4).
Any ideas of how to do this?

3
  • See also here for some more explanation Commented May 29, 2016 at 22:05
  • 6
    Variadic functions and currying are incompatible. Just don't do it! The accepted answer is a dirty hack. Use semi-variadic functions instead, where the variadic arguments are passed within an array. Commented Jun 24, 2016 at 21:42
  • 1
    The functions in JavaScript are variadic by default, so hacking the language in a such way is harmful, because it may confuse newbies that getting multi-arity fns like this is the way, while it's definitely not. There are more smart ways to learn how can we use valueOf method. Commented Apr 18, 2017 at 12:35

19 Answers 19

85

Not sure if I understood what you want, but

function sum(n) {
  var v = function(x) {
    return sum(n + x);
  };

  v.valueOf = v.toString = function() {
    return n;
  };

  return v;
}

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

JsFiddle

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

8 Comments

what does the + before the function call means?
The + casts the result of sum() to number. In that case JavaScript calls the .valueOf method of an object. Since I'm returning anonymous function, this helps to convert it to primitive type. As you can see, I overwrote the native .valueOf with my own.
Btw Firefox dont need + to perform casting :-). you can call it without and it will work.
@Yaroslav: It will work if the result is converted to string (eg. when using alert()). When using native console.log, you will see function () { return n + x; } in the console.
@maverick no, valueOf is called by JavaScript internals, e.g. when running the unary + operator. As per ES5 specs, the + runs the abstract ToNumber, which in turn calls abstract ToPrimitive which calls internal method ` [[DefaultValue]]` which finally calls valueOf. You can follow the call chain starting from here
|
37

This is an example of using empty brackets in the last call as a close key (from my last interview):

sum(1)(4)(66)(35)(0)()

function sum(firstNumber) {
    let accumulator = firstNumber;

    return function adder(nextNumber) {
        if (nextNumber === undefined) {
            return accumulator;
        } 
    
        accumulator += nextNumber;

        return adder;
    }
}

console.log(sum(1)(4)(66)(35)(0)());

2 Comments

Thank you so much. I was banging my head for this answer. I would be happy if you can explain the logic behind this and how it actually works?
@TarunNagpal I tried to update the solution to make it look as simple as possible
14

I'm posting this revision as its own post since I apparently don't have enough reputation yet to just leave it as a comment. This is a revision of @Rafael 's excellent solution.

function sum (n) {
    var v = x => sum (n + x);
    v.valueOf = () => n; 
    return v;
}

console.log( +sum(1)(2)(3)(4) ); //10

I didn't see a reason to keep the v.toString bit, as it didn't seem necessary. If I erred in doing so, please let me know in the comments why v.toString is required (it passed my tests fine without it). Converted the rest of the anonymous functions to arrow functions for ease of reading.

2 Comments

toString means that console.log("The result is " + sum(1) + "!") works properly
console.log("The result is " + sum(1) + "!") returns the string ​The result is 1! even when the toString is omitted from the v.valueOf statement in the sum function, as I have done above. Still not clear on what adding toString would accomplish that isn't already happening.
10

New ES6 way and is concise.

You have to pass empty () at the end when you want to terminate the call and get the final value.

const sum= x => y => (y !== undefined) ? sum(x + y) : x;

call it like this -

sum(10)(30)(45)();

1 Comment

Nice! A variant which allows multiple arguments at each step, like sum (3, 4, 5) (22) (2, 6) () //=> 42, could look like this: const sum = (...xs) => (...ys) => ys .length ? sum (...xs, ...ys) : xs .reduce ((a, b) => a + b, 0).
8

Here is a solution that uses ES6 and toString, similar to @Vemba

function add(a) {
  let curry = (b) => {
    a += b
    return curry
  }
  curry.toString = () => a
  return curry
}

console.log(add(1))
console.log(add(1)(2))
console.log(add(1)(2)(3))
console.log(add(1)(2)(3)(4))

1 Comment

When I run that code in Chrome, I am getting (b) => { a += b; return curry; } in console.
6

Another slightly shorter approach:

const sum = a => b => b? sum(a + b) : a;

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

2 Comments

your code returns an error for this case: sum(1)(2)(0)()
@yury.hrynko kind of, 0 is falsy thus it also terminates the chain. Adding 0 is senseless anyways, though if you need this you can replace b? with b !== undefined ?
5

Here's a solution with a generic variadic curry function in ES6 Javascript, with the caveat that a final () is needed to invoke the arguments:

const curry = (f) =>
   (...args) => args.length? curry(f.bind(0, ...args)): f();
const sum = (...values) => values.reduce((total, current) => total + current, 0)
curry(sum)(2)(2)(1)() == 5 // true

Here's another one that doesn't need (), using valueOf as in @rafael's answer. I feel like using valueOf in this way (or perhaps at all) is very confusing to people reading your code, but each to their own.

The toString in that answer is unnecessary. Internally, when javascript performs a type coersion it always calls valueOf() before calling toString().


// invokes a function if it is used as a value
const autoInvoke = (f) => Object.assign(f, { valueOf: f } );

const curry = autoInvoke((f) =>
   (...args) => args.length? autoInvoke(curry(f.bind(0, ...args))): f());

const sum = (...values) => values.reduce((total, current) => total + current, 0)
curry(sum)(2)(2)(1) + 0 == 5 // true

Comments

4

Try this, this is more flexible to handle any type of input. You can pass any number of params and any number of paranthesis.

function add(...args) {
    function b(...arg) {
        if (arg.length > 0) {
            return add(...[...arg, ...args]);
        }

        return [...args, ...arg].reduce((prev,next)=>prev + next);
    }

    b.toString = function() {
        return [...args].reduce((prev,next)=>prev + next);
    }

    return b;
}

// Examples
console.log(add(1)(2)(3, 3)());
console.log(+add(1)(2)(3)); // 6
console.log(+add(1)(2, 3)(4)(5, 6, 7)); // 28
console.log(+add(2, 3, 4, 5)(1)());    // 15

Comments

3

Here's a more generic solution that would work for non-unary params as well:

const sum = function (...args) {
  let total = args.reduce((acc, arg) => acc+arg, 0)
  function add (...args2) {
    if (args2.length) {
      total = args2.reduce((acc, arg) => acc+arg, total)
      return add
    }
    return total
  }

  return add
}

document.write( sum(1)(2)() , '<br/>') // with unary params
document.write( sum(1,2)() , '<br/>') // with binary params
document.write( sum(1)(2)(3)() , '<br/>') // with unary params
document.write( sum(1)(2,3)() , '<br/>') // with binary params
document.write( sum(1)(2)(3)(4)() , '<br/>') // with unary params
document.write( sum(1)(2,3,4)() , '<br/>') // with ternary params

Comments

3

ES6 way to solve the infinite currying. Here the function sum will return the sum of all the numbers passed in the params:

const sum = a => b => b ? sum(a + b) : a

sum(1)(2)(3)(4)(5)() // 15

1 Comment

you will need to handle the b=0 case
3

we can also use this easy way.

function sum(a) {
    return function(b){
      if(b) return sum(a+b);
      return a;
    }
}

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

Comments

2

Try this

function sum (...args) {
  return Object.assign(
    sum.bind(null, ...args),
    { valueOf: () => args.reduce((a, c) => a + c, 0) }
  )
}

console.log(+sum(1)(2)(3,2,1)(16))

Here you can see a medium post about carried functions with unlimited arguments

https://medium.com/@seenarowhani95/infinite-currying-in-javascript-38400827e581

Comments

1
function add(a) {
    let curry = (b) => {
        a += b
        return curry;
    }
    curry[Symbol.toPrimitive] = (hint) => {
        return a;
    }
    return curry
}

console.log(+add(1)(2)(3)(4)(5));        // 15
console.log(+add(6)(6)(6));              // 18
console.log(+add(7)(0));                 // 7
console.log(+add(0));                    // 0

Comments

1

Here is another functional way using an iterative process

const sum = (num, acc = 0) => {
    if !(typeof num === 'number') return acc;
    return x => sum(x, acc + num)
}

sum(1)(2)(3)()

and one-line

const sum = (num, acc = 0) => !(typeof num === 'number') ? acc : x => sum(x, acc + num)

sum(1)(2)(3)()

2 Comments

your code returns an error with this case sum(1)(2)(0)()
Of I see, thanks. It's better to check the type of the number.
0

You can make use of the below function

function add(num){
   add.sum || (add.sum = 0) // make sure add.sum exists if not assign it to 0
   add.sum += num; // increment it
   return add.toString = add.valueOf = function(){ 
      var rtn = add.sum; // we save the value
      return add.sum = 0, rtn // return it before we reset add.sum to 0
   }, add; // return the function
}

Since functions are objects, we can add properties to it, which we are resetting when it's been accessed.

3 Comments

you didnt explain what you have written.. -1
@NaeemShaikh ok. If that is it, then it's fine :)
This is bad, because it leaves global state. x = add(1)(2); y = add(4); console.log(y) gives 7, not 4
-1

To make sum(1) callable as sum(1)(2), it must return a function.

The function can be either called or converted to a number with valueOf.

function sum(a) {

   var sum = a;
   function f(b) {
       sum += b;
       return f;
    }
   f.toString = function() { return sum }
   return f
}

Comments

-1
   function sum(a){
    let res = 0;
    function getarrSum(arr){
            return arr.reduce( (e, sum=0) =>  { sum += e ; return sum ;} )
     }

    function calculateSumPerArgument(arguments){
            let res = 0;
            if(arguments.length >0){

            for ( let i = 0 ; i < arguments.length ; i++){
                if(Array.isArray(arguments[i])){
                    res += getarrSum( arguments[i]);
                }
                else{
                  res += arguments[i];
                }
             }
          }
            return res;
     }
    res += calculateSumPerArgument(arguments);



    return function f(b){
        if(b == undefined){
            return res;
        }
        else{
            res += calculateSumPerArgument(arguments);
            return f;
        }
    }

}

1 Comment

I have written generic function .. it will run for any combination of number //and array. For calling Ex. sum(1)(2)() or sum(1)(2)(12,[3,4])() or sum(1,2,3,[12,4])(4,5,[1,2,3])(23)()
-1
let add = (a) => {
  let sum = a;
  funct = function(b) {
    sum += b;
    return funct;
  };

  Object.defineProperty(funct, 'valueOf', {
    value: function() {
      return sum;
    }
  });
  return funct;
};


console.log(+add(1)(2)(3))

1 Comment

Add some explanation of what it does.
-1

After looking over some of the other solutions on here, I would like to provide my two solutions to this problem.

Currying two items using ES6:

const sum = x => y => (y !== undefined ) ? +x + +y : +x
sum(2)(2) // 4

Here we are specifying two parameters, if the second one doesnt exist we just return the first parameter.

For three or more items, it gets a bit trickier; here is my solution. For any additional parameters you can add them in as a third

const sum = x => (y=0) => (...z) => +x + +y + +z.reduce((prev,curr)=>prev+curr,0)
sum(2)()()//2
sum(2)(2)()//4
sum(2)(2)(2)//6
sum(2)(2)(2,2)//8

I hope this helped someone

2 Comments

The whole idea is to make it work without writing any case. So this solution has no advantages over the more generic ones and has a disadvantage of a lot of code.
ah okay, I think ill go back and relearn this better then...thanks for that man

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.