16

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?

Rahil Wazir
  • 10,007
  • 11
  • 42
  • 64
johnM1993
  • 161
  • 1
  • 4
  • btw, do not use "for in" loops to iterate over arrays because the for-in loop has no guaranteed iteration order and it also can iterate over non-integer properties. Use a regular `for(i=0, i – hugomg Mar 08 '15 at 14:36
  • 12
    You're trying to do **way** to much here. As a learning exercise this might be fun, but I really hope you're not trying to use this outside of idle experimentation. This kind of ridiculous tricky syntax overloading has no place in real-world code. – user229044 Mar 08 '15 at 14:39
  • If you really need it, you can just add the function to the Number prototype. Or create a custom wrapper that extends the Number object so that you don't dirty the original prototype. Not quite sure why you can't just use `+` though.. – MDEV Mar 08 '15 at 14:39
  • @hugomg Though also note that `arguments` isn’t an array, it’s an array-like object. – Andrew Marshall Mar 08 '15 at 14:47
  • 7
    This is most definitely an exercise, the + operator would suffice fine :P – johnM1993 Mar 08 '15 at 14:48
  • _"Also they need to always return ints (not strings), and it seems like sometimes they do and other times they don't?"_ Can you give us an example? – JLRishe Mar 08 '15 at 14:55
  • See [Javascript sum function](http://stackoverflow.com/q/5832891/1048572) and the questions linked from there. – Bergi Mar 08 '15 at 14:58
  • Re: “Also they need to always return ints”, there are no integers in JavaScript, only floats. – Andrew Marshall Mar 08 '15 at 16:33
  • Off the topic. Can this be used as a interview question for testing advanced knowledge of functions in Javascript. There are so many things to test here. Function are objects, arguments object, function as return value and other stuff... – Vishwanath Mar 10 '15 at 05:22
  • [You should totally drop that and try jQuery](http://i.stack.imgur.com/ssRUr.gif) ([jMath](http://www.mikedoesweb.com/2012/jquery-math-finally-solved/)) – Cole Tobin Mar 10 '15 at 14:30
  • You won't be able to get `add(3) === 3` to work, by the way. – 1983 Mar 11 '15 at 11:49

4 Answers4

17

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>
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • `sum.toString.bind(sum);`? Clever. I wonder why you didn't do the same thing with `valueOf`… – Bergi Mar 08 '15 at 15:02
  • @Bergi Because it would require a needless boxing operation, but perhaps that's just unnecessary optimization, and `sum.toString.bind(sum)` also involves unnecessary boxing. – JLRishe Mar 08 '15 at 15:05
4

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

Vishwanath
  • 6,284
  • 4
  • 38
  • 57
3

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;
}
Mukesh Agarwal
  • 530
  • 4
  • 15
  • 1
    The implementation of the `add` method here introduces inconsistency because it has side-effects: https://jsfiddle.net/hjrj7mnq/1/ – JLRishe Mar 08 '15 at 15:57
3

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));
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
hugomg
  • 68,213
  • 24
  • 160
  • 246