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
4 Answers 4
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
1 Comment
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);
}
7 Comments
prototype.valueOf is a little dicey, and the leading with a + is a bit hacky, but otherwise very clever.+calc(1,2,3) then this refers to windowmap better use slice for getting array from arguments, and also instead this use some another nested function or objectIf 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.
Comments
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>
calc(1,2)should return3or maybe a function that adds3to whatever other parameter you pass it (socalc(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.calc(a,b)and you want to be able to docalc(1,2)andcalc(1)(2), then that's just currying and my linked question has several answers. If you function iscalc()and you want to be able to docalc(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 callcalc(1,2)to return a function or the sum of the values?