“add” function that works with different combinations of chaining/arguments

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>

Leave a Comment