Kindly read before you mark it as duplicate.
Im not asking for single curry call.
This functions multiplies, multiplication(4,4,4) //64
function multiplication(...args) {
return args.reduce((accum, val) => accum * val, 1)
}
But Im trying to achieve something else...
This same function should multiply its curry function parenthesis as well. e.g.
/*
which return the multiplication of three numbers.
The function can be called in any of the following forms:
multiply(2, 3)(4) => 24
multiply(2)(3, 4) => 24
multiply(2)(3)(4) => 24
multiply(2, 3, 4) => 24
*/
Kindly help.
After fiddling through a lot of code and reading some stack answers.
Finally I came up with. But it still doesnt satisfy this multiply(2)(3, 4) => 24
But works fine for rest of the cases
multiply(2,3,4)
multiply(2,3)(4)
multiply(2)(3)(4)
var multiply = function(...args) {
if (args.length === 3) {
return args[0] * args[1] * args[2];
} else {
return function() {
args.push([].slice.call(arguments).pop());
return multiply.apply(this, args);
};
}
}
while multiply(2)(3, 4) => 24 fail
Here's a generalized solution that works by repeatedly calling bind
until enough parameters have been passed.
function curry(func, arity = func.length) {
return function (...args) {
if (args.length >= arity) {
return func(...args);
} else {
return curry(func.bind(this, ...args), arity - args.length);
}
};
}
const multiply = curry((a, b, c) => a * b * c);
console.log(multiply(2, 3)(4));
console.log(multiply(2)(3, 4));
console.log(multiply(2)(3)(4));
console.log(multiply(2, 3, 4));
Your code
var multiply = function(...args) {
if (args.length === 3) {
return args[0] * args[1] * args[2];
} else {
return function() { // ***
args.push([].slice.call(arguments).pop()); // ***
return multiply.apply(this, args);
};
}
}
*** these two lines needed changing, you were almost there, so tantalisingly close in fact
var multiply = function(...args) {
if (args.length === 3) {
return args[0] * args[1] * args[2];
} else {
return function(...args2) { // ***
args.push(...args2); // ***
return multiply.apply(this, args);
};
}
}
console.log(multiply(2, 3)(4))
console.log(multiply(2)(3, 4))
console.log(multiply(2)(3)(4))
console.log(multiply(2, 3, 4))
ES6 makes it even cleaner
const multiply = (...args) => (args.length === 3) ? args[0] * args[1] * args[2] : (...args2) => multiply(...args.concat(args2));
console.log(multiply(2, 3)(4))
console.log(multiply(2)(3, 4))
console.log(multiply(2)(3)(4))
console.log(multiply(2, 3, 4))
Here's an answer similar to 4castle's that uses an additional rest parameter instead of Function.prototype.bind
const curry = (f, ...xs) => (...ys) =>
f.length > xs.length + ys.length
? curry (f, ...xs, ...ys)
: f (...xs, ...ys)
const multiply =
curry ((a, b, c) => a * b * c)
console.log (multiply (2, 3) (4)) // 24
console.log (multiply (2) (3, 4)) // 24
console.log (multiply (2) (3) (4)) // 24
console.log (multiply (2, 3, 4)) // 24
console.log (multiply () () () (2, 3, 4)) // 24
But relying upon the length
property is a function can be a problem when variadic functions come into play – Here, partial
is easier to understand, explicitly communicates when a function's arguments will not be supplied in entirety, and it works with variadic functions.
const multiply = (x, ...xs) =>
x === undefined
? 1
: x * multiply (...xs)
const partial = (f, ...xs) =>
(...ys) => f (...xs, ...ys)
console.log (partial (multiply) (2, 3, 4)) // 24
console.log (partial (multiply, 2) (3, 4)) // 24
console.log (partial (multiply, 2, 3) (4)) // 24
console.log (partial (multiply, 2, 3, 4) ()) // 24
console.log (multiply (2, 3, 4, 5, 6, 7)) // 5040
console.log (partial (multiply, 2, 3, 4) (5, 6, 7)) // 5040
console.log (partial (partial (multiply, 2, 3), 4, 5) (6, 7)) // 5040
Partial application is related to currying, but not exactly the same thing. I write about some of the differences in this answer and this one
Here is a minimal curry function
const curry = (fn, ...args) =>
args.length >= fn.length ? fn(...args) : curry.bind(null, fn, ...args)
High Level Explanation:
We want to construct a function, similar in spirit to Thrush ( f => a => f(a)
) but with variadic inputs. We want to partially apply input to this function, passing in the curried function f
for the first parameter and the rest of the parameters needed until the appropriate arity for our function, given by f.length
is met or exceeded.
Details:
Suppose we have some add function,
const add = (a,b,c) => a+b+c
and we curry it
const curriedAdd = curry( add )
Here is what happens:
curry( add, 10 )
)args.length >= fn.length
is false
because we provided no args
and the function has a length of 3Cool so we basically just get the same function back only now its bound to curry
Next we call it thus
const inc = curriedAdd(0,1)
Now the following happens
We invoke the curried function. curriedAdd
is add
bound to it as the first parameter (after this
is set to null
). It looks like this
const inc = curry.bind(null,add)(0,1)
Here when we invoke curry, add
is again the first parameter of the function. args
is now a list of two [0,1]
.
args.length >= fn.length
is therefore false because add.length
is 3 and args.length
is two. curry
and bind that to add
with two parameters [0,1]
spread into bind.inc
is not curry.bind(null, add, 0, 1)
Cool, so now we call this
const six = inc(5)
But inc
is just curry.bind(null,add,0,1)
Thus we called curry
as before. This time args.length >= fn.length
is true
and invoke add
with all three parameters
An important part of this currying function is that the predicate be args.length >= fn.length
and not args.length === fn.length
because otherwise this would fail
const six = inc(5,undefined)
This may seem not important, however, in Javascript you might often do something like this
const concatWith = curry( (fn,a,b) => a.concat(fn(b)) )
const collectObjectValues = concatWith(Object.values)
[ {a: 1, b: 2}, {c: 3} ].reduce( collectObjectValues, [] )
// [1,2,3]
The reduce
function passes in a few parameters... a number greater than our expected two (see footnotes). If our curry predicate didn't account for the greater than scenario, this code would break.
Hope this was informative and educational. Enjoy!
Footnotes:
[1] - four to be exact, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With