0

As most of us know we can create a simple function like this.

function calc(a,b){
   return a+b
}
calc(1,1); //returns 2

We can also make something like this

function calc(a){
    return function(b){
      return a+b
    }
}
calc(1)(1); //returns 2

What about if we had multiple arguments?

function calc() {
    function r(arg) {
        var a = [];
        for(var i = 0, l = arg.length; i < l; i++){
            a[i] = arg[i];
        }

        return a.reduce(function(p, c) {
          return p + c;
        });

    }
    var res = r(arguments);
    return function() {
        res += r(arguments);
        return res;
    }
}

This works for calc(1,2)(1) but it doesn't for calc(1,2,1)

Is there a way to combine both versions? That means that when calling calc(1,1) we could also call calc(1)(1) and both would still return 2.

Or calc(1,2,3) calc(1,2)(3) calc(1)(2,3) would all return 6

Marcio
  • 612
  • 6
  • 20
  • Use varargs, then it is possible. – meskobalazs Aug 05 '15 at 14:04
  • Somebody posted [this link](http://stackoverflow.com/a/31795868/1250301) in [another question](http://stackoverflow.com/questions/31795520/a-single-function-that-can-be-invoked-in-two-ways/31795640#31795640), about currying functions. It's worth a read. – Matt Burland Aug 05 '15 at 14:06
  • possible duplicate of [Javascript sum function](http://stackoverflow.com/questions/5832891/javascript-sum-function) – Grundy Aug 05 '15 at 14:14
  • You all think it's a duplicate. Try calculating multiple attributes. It's not gonna work the same way. It's a lot more complicated than it seems to be. – Marcio Aug 05 '15 at 14:15
  • 1
    You seem to be asking for a function that can return two different things without giving it any indication of what you want. So `calc(1,2)` should return `3` or maybe a function that adds `3` to whatever other parameter you pass it (so `calc(1,2)(3)` would work). That's impossible. You are asking for a function that can read minds. If you restrict it to a set number of parameters, then it's just currying which is certainly a duplicate. – Matt Burland Aug 05 '15 at 14:21
  • @MattBurland That's basically my question. If it would be possible in some way to do something like that. calc(infinite args)(infinite args) = some result. I was just wondering while playing around with code if it was possible somehow. – Marcio Aug 05 '15 at 14:23
  • 1
    If you have a function `calc(a,b)` and you want to be able to do `calc(1,2)` and `calc(1)(2)`, then that's just currying and my linked question has several answers. If you function is `calc()` and you want to be able to do `calc(1,2)`, `calc(1)(2)`, `calc(1,2)(3)`, `calc(1,2)(3,4)`, then that is impossible. How would your function know when you call `calc(1,2)` to return a function or the sum of the values? – Matt Burland Aug 05 '15 at 14:26
  • @MattBurland My solution is kinda close to what i asked so it's still impossible but still not far from it. – Marcio Aug 06 '15 at 11:26

4 Answers4

1

It needs to know how many arguments to do the calculation after ;-)

You can make a function that turns things into that sort of function something like this :

function curry(f){
  var args = [];
  return addargs;
  function addargs(){
    args=args.concat([].slice.call(arguments));
    return args.length<f.length? addargs.bind(this) : f.apply(this,args);
  }
}

Then you can do something like this :

var calc = curry(function(a,b,c){return a+b+c});
calc(1,2)(3); // = 6

but you can't have it take a variable number of arguments and curry those - it wouldn't know when to return the answer

imma
  • 4,912
  • 2
  • 17
  • 10
  • 2
    ie you can't have calc(1,2) give 3 and calc(1,2)(3) give 6 because calc(1,2)(3) would be doing 6(3) and 6 isn't a function – imma Aug 05 '15 at 14:27
1

So here's the closest i've come to my problem. With the code below it works if i add a + sign in front of the function so +calc(1,2,3) +calc(1,2)(3) +calc(1)(2)(3) +calc(1,2)(1)(3,0,1) will all work

function calc(){
    var args = [].map.call(arguments, function(a) {
        return a;
    });
    var recursiveSum = function(arr) {
        return +arr.reduce(function(a, b) {
            return +a + (b && b.reduce ? recursiveSum(b) : +(isNaN(b) ? 0 : b));
        }, 0);
    };
    calc.__proto__.valueOf = function() {
        return recursiveSum(args);
    };
    return calc.bind(this, args);
}
Marcio
  • 612
  • 6
  • 20
  • Overriding the `prototype.valueOf` is a little dicey, and the leading with a `+` is a bit hacky, but otherwise very clever. – Matt Burland Aug 06 '15 at 13:01
  • in your case when call like `+calc(1,2,3)` then `this` refers to `window` – Grundy Aug 06 '15 at 18:34
  • @Grundy if you have some improvement let me know. It's just for fun code i'm not gona use this in production obviously. – Marcio Aug 07 '15 at 02:48
  • i think instead `map` better use `slice` for getting array from _arguments_, and also instead `this` use some another nested function or object – Grundy Aug 07 '15 at 06:10
  • @Grundy Why do you think slice would be better than map? Any advantage one over the other in some way? Not sure what you mean with this replacing with object or nested function. – Marcio Aug 07 '15 at 06:49
  • i think `slice` simplest and faster that `map` for transforming array like object to array – Grundy Aug 07 '15 at 07:12
  • as for `this` as i say you should not use it in this way, because you pollute global `window` object, and also `Function.prototype`. I mean you can declare some nested function, like in my answer – Grundy Aug 07 '15 at 07:15
0

If it would be possible in some way to do something like that. calc(infinite args)(infinite args) = some result

This is maybe the closest I can imagine to what you want:

function calc(a,b) {                     // we expect a and b to be arrays
    if (Array.isArray(a)) {              // check if a is an array
        if (b === undefined) {           // if we don't have a b
            return calc.bind(null, a);   // return a curried function
        }
        if (!Array.isArray(b)) {         // if b isn't an array
            return calc(a, [].splice.call(arguments,1));    // turn all the arguments 
                                                            // after a into an array and 
                                                            // call the function again
        }
        // if both a and b are arrays, just calculate the sum and return it
        var aSum = a.reduce(function(p,c) { return p + c },0);
        return b.reduce(function(p,c) { return p + c}, aSum);
    }
    // if a was not an array, turn the arguments into an array and call again
    return calc([].splice.call(arguments,0));
}

console.log(calc(1,2)(null));      // 3
console.log(calc(1,2)(3,4));       // 10
console.log(calc(1)(3,4));         // 8
console.log(calc(1,2,3)(null));    // 6

The limitation here is that can't do calc(1,2) because it returns a function, so if you want the result, the only way to do it is to call it and pass null.

The idea here is that we have a function that will take two arrays, a and b and sum them together. If a isn't an array, we will take all the arguments that were passed and turn it into an array and then call the function again passing that array as a.

If a is and array and b is undefined, then we return a function curried with a. If a is an array and b isn't an array, then we turn all the arguments (except a) into and array and call the function again. If b is an array then we just do the calculation and return the result.

Matt Burland
  • 44,552
  • 18
  • 99
  • 171
-1

Yet another way

function sum() {
  function add(a, b) {
    return a + b;
  };

  var num = [].reduce.call(arguments, add);

  function inner() {
    return sum.apply(this, [].concat.apply([num], arguments));
  }
  inner.toString = inner.valueOf = inner.toJSON = function() {
    return num;
  }
  return inner;
}

function o(prepend, val){
  document.getElementById('res').innerHTML += (prepend? (prepend+": "):"")+(typeof val == 'string'? val : JSON.stringify(val)) + '<br />';
}

o('sum(1,2,3)(4,5,6)(7)',sum(1,2,3)(4,5,6)(7));
o('sum(1,2,3)',sum(1,2,3));
o('sum(1)(2,3)', sum(1)(2,3));
o('sum(1,2)(3)',sum(1,2)(3));
o('sum(1,2)(3)+"4"',sum(1,2)(3)+"4");
o('sum(1)(2,3)+4',sum(1)(2,3)+4); //10
<div id="res"></div>
Grundy
  • 13,356
  • 3
  • 35
  • 55