johnM1993
johnM1993

Reputation: 161

"add" function that works with different combinations of chaining/arguments

I'm trying to write an add function that will work in many scenarios.

add(2,2,2) //6
add(2,2,2,2) //8
add(2)(2)(2) // 6
add(2)(2)(2,2).value() //8
add(2,2)(2) + 2 //8
add(2).add(2) //4
add(2,2,2).add(2).add(2,2).value() //12
add(2,2,2).add(2).value() //8

This is what I have so far:

function add(){
    var sum = 0;
    for( var i in arguments ){
        sum += arguments[i];
    }

    var ret = add.bind(null, sum);

    ret.value = function () {
      return sum;
    }

    ret.add = function () {
      for( var i in arguments ){
        sum += arguments[i];
      }
      return sum;
    }

    ret.valueOf = function(){ return sum; };
    
    return ret;
}

console.log(add(2,2,2));
console.log(add(2,2,2,2));
console.log(add(2)(2)(2));
console.log(add(2)(2)(2,2).value());
console.log(add(2,2)(2) + 2);
console.log(add(2).add(2));
console.log(add(2,2,2).add(2).value());
console.log(add(2,2,2).add(2).add(2,2).value());

I am having a problem with the last two cases:

  add(2,2,2).add(2).add(2,2).value() //12
  add(2,2,2).add(2).value() //8

It seems like I would have to keep nesting the add functions if I wanted to chain more than two together and also add the value function to each of them, but obviously I'm missing something simple that will allow me to chain them as much as I like, and call value on any of them.

Also they need to always return ints (not strings), and it seems like sometimes they do and other times they don't?

Upvotes: 16

Views: 3498

Answers (4)

JLRishe
JLRishe

Reputation: 101730

Looking at the way you're using arguments in similar ways in two different places, it's clear that you are duplicating functionality and that is why you are running into this problem with having to "infinitely nest" the .value() method.

The key thing to recognize is that add() can return a function that references itself as its own add property. This will allow add(1,2)(3) to behave exactly the same as add(1,2).add(3). This can be done like so:

function add() {
  var sum = Array.prototype.reduce.call(arguments, function(l, r) {
    return l + r;
  }, 0);

  var ret = add.bind(null, sum);
  ret.add = ret;

  ret.value = ret.valueOf = Number.prototype.valueOf.bind(sum);      
  ret.toString = Number.prototype.toString.bind(sum);

  return ret;
}

snippet.log(add(2,2,2));
snippet.log(add(2,2,2,2));
snippet.log(add(2)(2)(2));
snippet.log(add(2)(2)(2,2).value());
snippet.log(add(2,2)(2) + 2);
snippet.log(add(2).add(2));
snippet.log(add(2,2,2).add(2).value());
snippet.log(add(2,2,2).add(2).add(2,2).value());

snippet.log(add(1, 2, 3)(4, 5).add(6, 7)(8).add(9, 10));
snippet.log(add(5,4)(3).add(2)(1) * 10);
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>


There are still two potential issues with the above approach, one minor and one a little less minor:

  • There are property references and function definitions that are re-executed every time the add function is used (including during chaining)
  • If someone overwrites the add identifier, it would cause the whole implementation to break:

function add() {
  var sum = Array.prototype.reduce.call(arguments, function(l, r) {
    return l + r;
  }, 0);

  var ret = add.bind(null, sum);
  ret.add = ret;

  ret.value = ret.valueOf = Number.prototype.valueOf.bind(sum);
  ret.toString = Number.prototype.toString.bind(sum);

  return ret;
}

var myAdd = add;
add = "boom!";
myAdd(1, 2, 3); // TypeError: add.bind is not a function

Both of these can be remedied with an IIFE:

var add = (function () {
    var reduce = Array.prototype.reduce,
        np = Number.prototype,
        valueOf = np.valueOf,
        toString = np.toString,
        plus = function (l, r) { return l + r; };

    return function add() {
        var sum = reduce.call(arguments, plus, 0);

        var ret = add.bind(null, sum);
        ret.add = ret;

        ret.value = ret.valueOf = valueOf.bind(sum);      
        ret.toString = toString.bind(sum);

        return ret;
    }
})();

var myAdd = add;
add = "U Can't Touch This";   // hammertime

snippet.log(myAdd(1, 2, 3)(4, 5).add(6, 7)(8).add(9, 10));
snippet.log(myAdd(5,4)(3).add(2)(1) * 10);
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>

Upvotes: 17

Vishwanath
Vishwanath

Reputation: 6004

I have tried to improvise with the use of this. Works for all cases.

function add(){
    var sum =  this instanceof Number?this: 0;
    for( var i in arguments ){
        sum += arguments[i];
    }

    var ret = add.bind(sum);
    ret.add = ret;
    ret.value = ret.valueOf = function() { return sum; };

    ret.toString = sum.toString.bind(sum);

    return ret;
}

JS-Fiddle

Upvotes: 4

hugomg
hugomg

Reputation: 69954

Also they need to always return ints (not strings), and it seems like sometimes they do and other times they don't?

Yeah, this is definitely a conceptual problem. These two things you want aren't compatible. Is add(2,2,2) a number or something with an add method?

add(2,2,2) //6
add(2,2,2).add(2).value() //8

Even if there is a fancy way to add methods to nubmers, I would highly recommend keeping things simple and always requiring a ".value()" call to end the chain. This way all calls to ".add" return an "adder object" and all calls to ".value" return a regular number.

it seems like I would have to keep nesting the add functions if I wanted to chain more than two together and also add the value function to each of them, but obviously I'm missing something simple that will allow me to chain them as much as I like, and call value on any of them.

The answer to this is to use recursive functions. Here is a function that creates the "adder" object I mentioned previously:

function sumArray(arr){
    var s = 0;
    for(var i=0; i<arr.length; i++){
        s += arr[i];
    }
    return s;
}

function mkAdder(currSum){
    return {
        value: function(){
            return currSum;
        },
        valueOf: function(){
            return currSum;
        },
        add: function(/**/){
            return mkAdder(currSum + sumArray(arguments));
        }
    }
}

Then your initial add function would look like this:

function add(/**/){
    return mkAdder(sumArray(arguments));
}

Upvotes: 3

Mukesh Agarwal
Mukesh Agarwal

Reputation: 528

Since you are returning the sum in the ret.add function thats why the error is coming try something like this, hope it will solve your problem

function add(){
    var sum = 0;
    for( var i in arguments ){
        sum += arguments[i];
    }

    var ret = add.bind(null, sum);

    ret.value = function () {
      return sum;
    }

    ret.add = function () {
      for( var i in arguments ){
        sum += arguments[i];
      }
      return ret;
    }

    ret.valueOf = function(){ return sum; };

    return ret;
}

Upvotes: 3

Related Questions