Reputation: 2884
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
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
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
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