Reputation: 28076
Using ES5, how do you curry a function that takes infinite arguments.
function add(a, b, c) {
return a + b + c;
}
The function above takes only three arguments but we want our curried version to be able to take infinite arguments.
Hence, of all the following test cases should pass:
var test = add(1);
test(2); //should return 3
test(2,3); //should return 6
test(4,5,6); //should return 16
Here is the solution that I came up with:
function add(a, b, c) {
var args = Array.prototype.slice.call(arguments);
return function () {
var secondArgs = Array.prototype.slice.call(arguments);
var totalArguments = secondArgs.concat(args);
var sum = 0;
for (i = 0; i < totalArguments.length; i++) {
sum += totalArguments[0];
}
return sum;
}
}
However, I have been told that it's not very “functional” in style.
Upvotes: 9
Views: 17755
Reputation: 11
This is the solution for infinite currying with single argument
function sum(x) {
return function(y) {
if (y !== undefined) {
return sum(x + y);
}
return x;
}
}
sum(1)(2)(3)(4)() //10
The solution for infinite argument currying
let sum = function (...args1) {
let total =args1.reduce((total,num) => total + num,0)
return function(...args2) {
if(args2.length!== 0) {
let total2 = args2.reduce((total,num)=>total + num,0);
return sum(total,total2);
}
return total;
};
};
console.log(sum(2,3,4)(2,3)(1)()); // 15
And a simple variadic function
function sum() {
let args = [...arguments];
let total = args.reduce((total,num) => total + num,0);
return total;
}
console.log(sum(1,2,3,4)) // 10
Upvotes: 1
Reputation: 1177
Simple solution
const add = (one) => { // one: Parameter passed in test
return (...args) => {
// args: Array with all the parameters passed in test
return one + args.reduce((sum, i) => sum + i, 0) // using reduce for doing sum
}
}
var test = add(1);
console.log(test(2)); //should return 3
console.log(test(2, 3)); //should return 6
console.log(test(4, 5, 6)); //should return 16
Upvotes: 0
Reputation: 11
Infinite sum with currying, you can pass a single parameter or multiple up-to infinite:
function adding(...arg) {
return function clousureReturn(...arg1) {
if (!arguments.length) {
let finalArr = [...arg, ...arg1];
let total = finalArr.reduce((sum, ele) => sum + ele);
return total;
}
return adding(...arg, ...arg1)
}
}
Upvotes: 1
Reputation: 141
Similar to the above problem. Sum of nth level curry by recursion
Trick: To stop the recursion I'm passing last () as blank**
function sum(num1) {
return (num2) => {
if(!num2) {
return num1;
}
return sum(num1 + num2);
}
}
console.log('Sum :', sum(1)(2)(3)(4)(5)(6)(7)(8)())
Upvotes: 12
Reputation: 739
Bit late in this game, but here is my two cents. Basically this exploits the fact that functions are also objects in JavaScript.
function add(x) {
if (x === undefined) {
return add.numbers.reduce((acc, elem) => acc + elem, 0);
} else {
if (add.numbers) {
add.numbers.push(x);
} else {
add.numbers = [x];
}
}
return add;
}
Upvotes: -1
Reputation: 18898
Part of the reason your add
function is not very "functional" is because it is attempting to do more than just add up numbers passed to it. It would be confusing for other developers to look at your code, see an add
function, and when they call it, get a function returned to them instead of the sum.
For example:
//Using your add function, I'm expecting 6
add(1,2,3) //Returns another function = confusing!
The functional approach would be to create a function that allows you to curry any other functions, and simplify your add function
:
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function () {
return fn.apply(this, args.concat(
Array.prototype.slice.call(arguments, 0)
));
}
}
function add() {
var args = Array.prototype.slice.call(arguments);
return args.reduce(function (previousValue, currentValue) {
return previousValue + currentValue;
});
}
Now, if you want to curry this function, you would just do:
var curry1 = curry(add, 1);
console.log(
curry1(2), // Logs 3
curry1(2, 3), // Logs 6
curry1(4, 5, 6) // Logs 16
);
//You can do this with as many arguments as you want
var curry15 = curry(add, 1,2,3,4,5);
console.log(curry15(6,7,8,9)); // Logs 45
If I still want to add 1, 2, 3
up I can just do:
add(1,2,3) //Returns 6, AWESOME!
This code is now becoming reusable from everywhere.
You can use that curry function to make other curried function references without any additional hassle.
Sticking with the math theme, lets say we had a multiply function that multiplied all numbers passed to it:
function multiply() {
var args = Array.prototype.slice.call(arguments);
return args.reduce(function (previousValue, currentValue) {
return previousValue * currentValue;
});
}
multiply(2,4,8) // Returns 64
var curryMultiply2 = curry(multiply, 2);
curryMultiply2(4,8) // Returns 64
This functional currying approach allows you take that approach to any function, not just mathematical ones. Although the supplied curry
function does not support all edge cases, it offers a functional, simple solution to your problem that can easily be built upon.
Upvotes: 9
Reputation: 74204
partial
A simple solution would be to use partial
as follows:
Function.prototype.partial = function () {
var args = Array.prototype.concat.apply([null], arguments);
return Function.prototype.bind.apply(this, args);
};
var test = add.partial(1);
alert(test(2)); // 3
alert(test(2,3)); // 6
alert(test(4,5,6)); // 16
function add() {
var sum = 0;
var length = arguments.length;
for (var i = 0; i < length; i++)
sum += arguments[i];
return sum;
}
If you only want one level of currying then this is what I would do:
var test = add(1);
alert(test(2)); // 3
alert(test(2,3)); // 6
alert(test(4,5,6)); // 16
function add() {
var runningTotal = 0;
var length = arguments.length;
for (var i = 0; i < length; i++)
runningTotal += arguments[i];
return function () {
var sum = runningTotal;
var length = arguments.length;
for (var i = 0; i < length; i++)
sum += arguments[i];
return sum;
};
}
Now, here's a more general solution with infinite levels of currying:
var add = running(0);
var test = add(1);
alert(+test(2)); // 3
alert(+test(2,3)); // 6
alert(+test(4,5,6)); // 16
function running(total) {
var summation = function () {
var sum = total;
var length = arguments.length;
for (var i = 0; i < length; i++)
sum += arguments[i];
return running(sum);
}
summation.valueOf = function () {
return total;
};
return summation;
}
A running total is the intermediate result of a summation. The running
function returns another function which can be treated as a number (e.g. you can do 2 * running(21)
). However, because it's also a function you can apply it (e.g. you can do running(21)(21)
). It works because JavaScript uses the valueOf
method to automatically coerce objects into primitives.
Furthermore, the function produced by running
is recursively curried allowing you to apply it as many times to as many arguments as you wish.
var resultA = running(0);
var resultB = resultA(1,2);
var resultC = resultB(3,4,5);
var resultD = resultC(6,7,8,9);
alert(resultD + resultD(10)); // 100
function running(total) {
var summation = function () {
var sum = total;
var length = arguments.length;
for (var i = 0; i < length; i++)
sum += arguments[i];
return running(sum);
}
summation.valueOf = function () {
return total;
};
return summation;
}
The only thing you need to be aware of is that sometimes you need to manually coerce the result of running
into a number by either applying the unary plus operator to it or calling its valueOf
method directly.
Upvotes: 7
Reputation: 3078
There is more generic approach by defining a curry function that takes minimum number of arguments when it evaluates the inner function. Let me use ES6 first (ES5 later), since it makes it more transparent:
var curry = (n, f, ...a) => a.length >= n
? f(...a)
: (...ia) => curry(n, f, ...[...a, ...ia]);
Then define a function that sums all arguments:
var sum = (...args) => args.reduce((a, b) => a + b);
then we can curry it, telling that it should wait until at least 2 arguments:
var add = curry(2, sum);
Then it all fits into place:
add(1, 2, 3) // returns 6
var add1 = add(1);
add1(2) // returns 3
add1(2,3) // returns 6
add1(4,5,6) // returns 16
You can even skip creating add
by providing the first argument(s):
var add1 = curry(2, sum, 1);
ES5 version of curry is not as pretty for the lack of ...
operator:
function curry(n, f) {
var a = [].slice.call(arguments, 2);
return a.length >= n
? f.apply(null, a)
: function () {
var ia = [].slice.call(arguments);
return curry.apply(null, [n, f].concat(a).concat(ia));
};
}
function sum() {
return [].slice.call(arguments).reduce(function (a, b) {
return a + b;
});
};
The rest is the same...
Note: If efficiency is a concern, you may not want to use slice
on arguments
, but copy it to a new array explicitly.
Upvotes: 0