zorza
zorza

Reputation: 2884

Compose javascript functions with more than one argument

How can I convert this function composition into more readable format?

funcA(argumentA, funcB(argumentA, funcC(argumentA, argumentB)))

What I'd like to achieve is something more like this:

compose(funcC, funcB, funcA)(argumentA, argumentB)

I'm using this compose function implementation:

const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)))

The problem is I need argumentA in all function calls as a first parameter and every function returns a value to be passed as a second parameter to the next function. I know I could create separate function-returning functions and use them like so:

compose(funcCWithArg(argumentA), funcBWithArg(argumentA), funcAWithArg(argumentA))(argumentB)

but in my actual case there aren't only three of them, but many more and that would require some serious amount of code just to write them down. Is there a simpler way to do that?

EDIT: I can't use any external library. Only vanilla js.

Upvotes: 6

Views: 2447

Answers (3)

Scott Sauyet
Scott Sauyet

Reputation: 50787

It's not hard to write a function like this:

const link = (...fns) => (a, ...args) =>
    fns.slice(1).reduce((val, fn) => fn(a, val), fns[0](a, ...args));

If you wanted it to be robust enough to handle errors gracefully, it would take more. But this should be a start.

You would use it like this:

const funcA = (x, y) => `funcA(${x}, ${y})`;
const funcB = (x, y) => `funcB(${x}, ${y})`;
const funcC = (x, y) => `funcC(${x}, ${y})`;

link(funcA, funcB, funcC)('argumentA', 'argumentB');
//=> "funcC(argumentA, funcB(argumentA, funcA(argumentA, argumentB)))"

You can see this in action on Runkit.

(And obviously you can do a reverse if you want the opposite argument order.)

Upvotes: 2

Chitharanjan Das
Chitharanjan Das

Reputation: 1333

Using vanilla JS,

const compose = (...fns) => (arg1, arg2) => fns.reduce((arg, f) => f(arg1, arg), arg2);

Explanation

compose becomes a function returning a function, which loops through the list of functions passed to it, passing the first argument to every function call.

Test

const sum = (a, b) => (a + b);
const mult = (a, b) => (a * b);
compose(sum, mult)(2, 3) === mult(2, sum(2, 3));  // true

Upvotes: 3

user94559
user94559

Reputation: 60143

First create new functions that are partial applications using bind. Then use the compose function you already have:

const funcA = (x, y) => `A(${x}, ${y})`;
const funcB = (x, y) => `B(${x}, ${y})`;
const funcC = (x, y) => `C(${x}, ${y})`;

const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
const partials = (...fns) => (...args) => fns.map((f) => f.bind(this, ...args));

console.log(compose(...partials(funcA, funcB, funcC)("a"))("b"));

// Output:
// A(a, B(a, C(a, b)))

UPDATE

You can also build a single function that composes the partials with the first argument passed and then calls them with the other arguments. (I think this is what you want? I wasn't 100% sure what to do with more than two arguments.)

const partialCompose = (...fns) => (...args) => compose(...partials(...fns)(args[0]))(...args.slice(1));

console.log(partialCompose(funcA, funcB, funcC)("a", "b")); // same output as above

Upvotes: 1

Related Questions