简体   繁体   中英

How does compose function work with multiple parameters?

Here's a 'compose' function which I need to improve:

const compose = (fns) => (...args) => fns.reduceRight((args, fn) => [fn(...args)], args)[0];

Here's a practical implementation of one:

 const compose = (fns) => (...args) => fns.reduceRight((args, fn) => [fn(...args)], args)[0]; const fn = compose([ (x) => x - 8, (x) => x ** 2, (x, y) => (y > 0 ? x + 3 : x - 3), ]); console.log(fn("3", 1)); // 1081 console.log(fn("3", -1)); // -8

And here's an improvement my mentor came to.

const compose = (fns) => (arg, ...restArgs) => fns.reduceRight((acc, func) => func(acc, ...restArgs), arg);

If we pass arguments list like that func(x, [y]) with first iteration, I still don't understand how do we make function work with unpacked array of [y]?

Let's analyse what the improved compose does

compose = (fns) =>
          (arg, ...restArgs) =>
          fns.reduceRight((acc, func) => func(acc, ...restArgs), arg);

When you feed compose with a number of functions, you get back... a function. In your case you give it a name, fn .

What does this fn function look like? By simple substitution you can think of it as this:

(arg, ...restArgs) => fns.reduceRight((acc, func) => func(acc, ...restArgs), arg);

where fns === [(x) => x - 8, (x) => x ** 2, (x, y) => (y > 0 ? x + 3 : x - 3)] .

So you can feed this function fn with some arguments, that will be "pattern-matched" against (arg, ...restArgs) ; in your example, when you call fn("3", 1) , arg is "3" and restArgs is [1] (so ...restArgs expands to just 1 after the comma, so you see that fn("3", 1) reduces to

fns.reduceRight((acc, func) => func(acc, 1), "3");

From this you see that

  1. the rightmost function, (x, y) => (y > 0 ? x + 3 : x - 3) is called with the two arguments "3" (the initial value of acc ) and 1 ,
  2. the result will be passed as the first argument to the middle function with the following call to func ,
  3. and so on,

but the point is that the second argument to func , namely 1 , is only used by the rightmost function, whereas it is passed to but ignored by the other two functions!

Conclusion

Function composition is a thing between unary functions. Using it with functions with higher-than-1 arity leads to confusion.

For instance consider these two functions

square = (x) => x**2;   // unary
plus = (x,y) => x + y;  // binary

can you compose them? Well, you can compose them into a function like this

sum_and_square = (x,y) => square(plus(x,y));

the compose function that you've got at the bottom of your question would go well:

sum_and_square = compose([square, plus]);

But what if your two functions were these?

apply_twice = (f) => ((x) => f(f(x))); // still unary, technically
plus = (x,y) => x + y;                 // still binary

Your compose would not work.

Even though, if the function plus was curried, eg if it was defined as

plus = (x) => (y) => x + y

then one could think of composing them in a function that acts like this:

f = (x,y) => apply_twice(plus(x))(y)

which would predictably produce result(3,4) === 10 .

You can get it as f = compose([apply_twice, plus]) .

A cosmetic improvement

Additionally, I would suggest a "cosmetic" change: make compose accept ...fns instead of fns ,

compose = (...fns)/* I've only added the three dots on this line */ =>
          (arg, ...restArgs) =>
          fns.reduceRight((acc, func) => func(acc, ...restArgs), arg);

and you'll be able to call it without groupint the functions to be composed in an array, eg you'd write compose(apply_twice, plus) instead of compose([apply_twice, plus]) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM