MicFin
MicFin

Reputation: 2501

Javascript function challenge add(1,2) and add(1)(2) both should return 3

A friend of mine challenged me to write a function that works with both of these scenarios

add(2,4)  // 6
add(2)(4) // 6

My instinct was the write an add() function that returns itself but I'm not sure I'm heading in the right direction. This failed.

function add(num1, num2){
    if (num1 && num2){
        return num1 + num2;
    } else {
        return this;
    }
}

alert(add(1)(2));

So I started reading up on functions that return other functions or return themselves.

I am going to keep trying, but if someone out there has a slick solution, I'd love to see it!

Upvotes: 6

Views: 13824

Answers (8)

Patrick Roberts
Patrick Roberts

Reputation: 51916

I wrote a curried function whose valueOf() method and function context (this) are bound with the sum no matter how many arguments are passed each time.

/* add function */
let add = function add(...args) {
  const sum = args.reduce((acc, val) => acc + val, this);
  const chain = add.bind(sum);
  chain.valueOf = () => sum;
  return chain;
}.bind(0);

/* tests */
console.log('add(1, 2) = ' + add(1, 2));
console.log('add(1)(2) = ' + add(1)(2));
/* even cooler stuff */
console.log('add(1, 2)(3) = ' + add(1, 2)(3));
console.log('add(1, 2, 3)(4, 5)(6) = ' + add(1, 2, 3)(4, 5)(6));
/* retains expected state */
let add7 = add(7);
console.log('let add7 = add(7)');
console.log('add7(3) = ' + add7(3));
console.log('add7(8) = ' + add7(8));

The reason why both mechanisms are required is because the body of add() must use the called function's bound context in order to access the sum of the intermediate partial application, and the call site must use the valueOf() member (either implicitly or explicitly) in order to access the final sum.

Upvotes: 11

trincot
trincot

Reputation: 350760

In general you need to have an agreement whether the function should return a function (for calling with more arguments) or the end result. Imagine the add function would have to work like this as well:

add(1, 2, 3)(4, 5)  // -> 15

...then it becomes ambiguous, because you might want to call again:

add(1, 2, 3)(4, 5)(6)  // -> 21

...and so add(1, 2, 3)(4, 5) should have returned a function, and not 15.

You could for instance agree that you have to call the function again, but without arguments, in order to get the numeric result:

function add(...args) {
    if (args.length === 0) return 0;
    let sum = args.reduce((a, b) => a+b, 0);
    return (...args) => args.length ? add(sum, ...args) : sum; 
}

console.log(add()); // 0
console.log(add(1,2,3)()); // 6
console.log(add(1,2,3)(4,5)()); // 15
console.log(add(1,2,3)(4,5)(6)()); // 21

Upvotes: 0

Rafiq
Rafiq

Reputation: 11485

One may think that he/she has to invoke the same function two times, but if you think deeply you will realize that the problem is pretty straight forward, you have to invoke the add function one time then you need to invoke what ever the add function returns.

function add(a){
    return function(b){
        return a+b;
    }
}
console.log(add(20)(20));
//output: 40

you can return function as many as time you want. suppose for y = mx+c

const y= function (m){
    return function(x){
        return function (c){
            return m*x+c
        }
    }
}

console.log(y(10)(5)(10)); 
//out put: 60

Upvotes: -1

Rahul Arora
Rahul Arora

Reputation: 4543

We can use the concept of closures which is provided by Javascript.

Code snippet:

function add(a,b){

    if(b !== undefined){

        console.log(a + b);
        return;

    }

    return function(b){
        console.log(a + b);
    }

}

add(2,3);
add(2)(3);

Upvotes: 0

andih
andih

Reputation: 5603

There is an article on Dr.Dobs Journal about "Currying and Partial Functions in JavaScript" which describes exactly this problem.

One solution found in this article is:

// a curried add
// accepts partial list of arguments
function add(x, y) {
     if (typeof y === "undefined") { // partial
        return function (y) {
              return x + y;
        };
     }
   // full application
   return x + y;
}

Upvotes: 6

Dmitry Ivanov
Dmitry Ivanov

Reputation: 381

var add = function(){
  // the function was called with 2 arguments
  if(arguments.length > 1)
    arguments.callee.first_argument = arguments[0];

  // if the first argument was initialized
  if(arguments.callee.first_argument){
    var result = arguments.callee.first_argument + arguments[arguments.length - 1];
    arguments.callee.first_argument = 0;

    return result;
  }else{// if the function was called with one argument only then we need to memorize it and return the same function handler 
    arguments.callee.first_argument = arguments.callee.first_argument || arguments[0];
    return arguments.callee;
  }
}
console.log(add(2)(4));
console.log(add(2, 4));

An extended solution which depends on the environment:

function add(){
  add.toString = function(){
    var answer = 0;
    for(i = 0; i < add.params.length; i++)
      answer += add.params[i];
    return answer;
  };

  add.params = add.params || [];
  for(var i = 0; i < arguments.length; i++)
    add.params.push(arguments[i])

  return add;
}

console.log(add(2)(4)(6)(8))
console.log(add(2, 4, 6, 8));

Upvotes: 0

rgbchris
rgbchris

Reputation: 816

The concept that you're looking for is called currying and it has to do with function transformation and partial function application. This is useful for when you find yourself calling the same function over and over with mostly the same arguments.

An example of implementing add(2)(6) via currying would look something like this...

function add(x,y) { 
  if (typeof y === 'undefined') {
    return function(y) {
      return x + y;
    }
  }
}

add(2)(4); // => 6

Additionally, you could do something like this...

var add6 = add(6);
typeof add6; // => 'function'
add6(4);     // => 10

Upvotes: 0

simon574
simon574

Reputation: 78

function add(num1, num2){
    if (num1 && num2) {
        return num1 + num2;
    } else if (num1) {
        return function(num2){return num1 + num2;};
    }
    return 0;
}

Upvotes: 0

Related Questions