How to curry a function across an unknown number of parameters
Asked Answered
K

7

11

Say I have a function called multiplyDivide

If I were to call multiplyDivide(2)(3)(4)(6) it would be equivalent to 2 * 3 / 4 * 6.

Update: Is it possible to write a function like this if I don't know in advance how many parameters I will be taking? For example, I could have multiplyDivide(1)(2) or multiplyDivide(1)(2)(3)(4)...(n-1)(n)

Kazbek answered 28/8, 2016 at 2:49 Comment(1)
Hint: Nest 5 functions and then have a block inside the 5th doing the operation with the parameters of each of the functionsTourbillion
S
3

Using a functional approach, you can create a function that "curries" arguments for another function. You will need a way to tell the function to return the value, so in this case, calling the function without passing any arguments will return the result:

function curry(fn, ...values) {
    return (...next) => (next.length) ? curry(fn, ...values, ...next) : fn(...values);
}

The cool thing about this function is that you can pass multiple arguments and/or keep invoking the function (1)(2, 3, 4)(5).

Here's a couple of examples:

function curry(fn, ...values) {
  return (...next) => (next.length) ? curry(fn, ...values, ...next) : fn(...values);
}

function multiplyDivide(...args) {
  return args.reduce((total, next, i) => (i % 2) ? (total / next) : (total * next), args.shift());
}

let x = curry(multiplyDivide)(2)(3, 4)(6)();
console.log(x);

let y = curry(multiplyDivide)(5, 4, 2)(3);
y = y(3, 5)(1)();
console.log(y);

Of course, this example does hint at just simply overloading the multiplyDivide function and passing your values to that when you're ready:

function multiplyDivide(...args) {
  return args.reduce((total, next, i) => (i % 2) ? (total / next) : (total * next), args.shift());
}

const values = [5, 4, 2, 3, 3];
values.push(5, 1);

console.log(multiplyDivide(...values));
Saprolite answered 29/8, 2016 at 2:23 Comment(0)
T
20

It's sort of possible but you need to define the terminating condition because the problem is essentially the same problem as writing a recursive function. The function needs a way to tell whether it should return a function or a value.

How you signal the need for values is up to you. One way of doing it is to check if an argument is passed:

// Using add instead of multiplyDivide to simplify example:

function add (num) {
    function adder (n) {
        if (n !== undefined) {
            num += n;
            return adder;
        }
        else { // terminate
            return num;
        }
    }
    return adder;
}

Now you can do:

var sum = add(1)(2)(3)(4)();

Otherwise it would return a function which you can keep calling:

var x = add(1)(2)(3)(4);
x = x(5)(6)(7);
x = x(8)(9)(10);

var sum = x();

Since in js functions are objects, you can also implement the value getter as a static method. It won't be purely functional but makes the "API" a bit more explicit and easier to read:

function add (num) {
    function adder (n) {
        num += n;
        return adder;
    }
    adder.value = function(){
        return num
    };
    return adder;
}

Which would allow you to do:

var sum = add(1)(2)(3)(4).value();

You can even get fancy by overriding the built-in .valueOf() and .toString() methods:

function add (num) {
    function adder (n) {
        num += n;
        return adder;
    }
    adder.valueOf = function(){
        return num
    };
    adder.toString = function(){
        return '' + num
    };
    return adder;
}

Which would allow you to do:

var sum = add(1)(2)(3)(4) + 5; // results in 15
var txt = add(1)(2)(3)(4) + "hello"; // results in "10hello"

The key here is that you need a way to tell the function to stop returning functions.

Taproom answered 29/8, 2016 at 3:51 Comment(1)
I think this should be the accepted answer. It actually answers the question. The accepted answer doesn't even use currying. I had the same question and this post gave me the answer I was looking for. Thanks!Forelli
S
3

Using a functional approach, you can create a function that "curries" arguments for another function. You will need a way to tell the function to return the value, so in this case, calling the function without passing any arguments will return the result:

function curry(fn, ...values) {
    return (...next) => (next.length) ? curry(fn, ...values, ...next) : fn(...values);
}

The cool thing about this function is that you can pass multiple arguments and/or keep invoking the function (1)(2, 3, 4)(5).

Here's a couple of examples:

function curry(fn, ...values) {
  return (...next) => (next.length) ? curry(fn, ...values, ...next) : fn(...values);
}

function multiplyDivide(...args) {
  return args.reduce((total, next, i) => (i % 2) ? (total / next) : (total * next), args.shift());
}

let x = curry(multiplyDivide)(2)(3, 4)(6)();
console.log(x);

let y = curry(multiplyDivide)(5, 4, 2)(3);
y = y(3, 5)(1)();
console.log(y);

Of course, this example does hint at just simply overloading the multiplyDivide function and passing your values to that when you're ready:

function multiplyDivide(...args) {
  return args.reduce((total, next, i) => (i % 2) ? (total / next) : (total * next), args.shift());
}

const values = [5, 4, 2, 3, 3];
values.push(5, 1);

console.log(multiplyDivide(...values));
Saprolite answered 29/8, 2016 at 2:23 Comment(0)
S
0

Do you mean something like this?

var multiplyDivide = function(first){
  return function(second){
    return function(third){
      return function(forth){
        return first * second / third * forth;
      }
    }
  }
}
//should be 6
console.log(multiplyDivide(2)(4)(4)(3));
Settlement answered 28/8, 2016 at 2:57 Comment(3)
@NilesTurner Sorry, I wasn't very clear. I already am aware of how to do this with a fixed number of parameters, but I don't know how if it's variable.Kazbek
@AlanH that makes more sense, however if the amount of inputs is variable what is the pattern of operations? you suggested a * b / c * d, does it merely flip the operand for each additional input? so 5 inputs would be a* b / c * d / e?Settlement
Yeah, it just alternatesKazbek
P
0

This is in fact a very good question...

Currying is one of the most fundamental aspects of functional programming languages where you can pass around and return functions. JS being a functional programming language at some level, should be able to perform this operation.

So just like the one in your question one might want to curry an indefinitely many argument function for this or that reasons.

OK let your function be

function multiplyDivide (n,m,o,p){
  return n * m / o * p;
}

Then a way of implementing a utility curry function to obtain the curried version of any given function would be as follows;

var curry = f => f.length ? (...a) => curry(f.bind(f,...a)) : f(),

So let's see it in action

function multiplyDivide (n,m,o,p){
      return n * m / o * p;
    }

var curry = f => f.length ? (...a) => curry(f.bind(f,...a)) : f(),
    f     = curry(multiplyDivide);
 
 res1 = f(4,5,2,10),
 res2 = f(4)(5,2,10),
 res3 = f(4)(5)(2,10),
 res4 = f(4)(5)(2)(10),
 res5 = f(4,5)(2,10),
 res6 = f(4,5)(2)(10),
 res7 = f(4,5,2)(10),
 res8 = f(4,5)(2,10);
console.log(res1,res2,res3,res4,res5,res6,res7,res8);

There might be simpler ways but this is what i could have come up with.

Panathenaea answered 23/5, 2017 at 17:32 Comment(4)
Can you please explain following code - var curry = f => f.length ? (...a) => curry(f.bind(f,...a)) : f(), f = curry(multiplyDivide);Synopsize
@BHUVNESH KUMAR You may have a look at Partially Applied Functions use caseofbind.Panathenaea
@Redu, where does the (...a) syntax come from. I know of the arguments array that each function has but where does this come from? Would you care to explain, is this just some other way of unpacking the arguments?Shurlocke
@Shurlocke Yes your guess is right. It's called the rest parameters. Besides arrow functions do not have the arguments arraylike.Panathenaea
N
0

function addValues(a, b) {
   if(b!=undefined)
    return a + b;

   return function(b) {
       return a + b;
   }
}
let output1 = addValues(2)(4);  // 6
let output2 = addValues(2,1);   // 3
console.log(output1);
console.log(output2)
Narrows answered 1/8, 2019 at 6:0 Comment(0)
R
0
function multiply(a) {
   return function(b) {
       return b ? multiply(a*b) : a;
   }
}

console.log(multiply(2)());
console.log(multiply(2)(3)());
console.log(multiply(2)(3)(4)());
console.log(multiply(2)(3)(4)(5)());
Ronrona answered 9/11, 2021 at 7:13 Comment(0)
R
0
function add() {
let args = [...arguments];
  function addAll(){
   let args1 = [...arguments];
   return add(...args, ...args1);
 }

let total = args.reduce((total, value) => total+value);
addAll.value = total;
return addAll;
}

console.log(add(2)(3)(4).value); 
console.log(add(2)(3).value); 
Roodepoortmaraisburg answered 27/3, 2022 at 17:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.