Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a "compose" function #16

Closed
kevinbarabash opened this issue Nov 24, 2014 · 11 comments
Closed

Add a "compose" function #16

kevinbarabash opened this issue Nov 24, 2014 · 11 comments

Comments

@kevinbarabash
Copy link

Great library. "compose" would be good. Maybe "pipe" too so that you send values through a series of functions.

@tjmehta
Copy link
Owner

tjmehta commented Nov 24, 2014

Yes! Great suggestion definitely need a compose method. What do you mean by pipe - does array.map suffice?


Sent from Mailbox

On Mon, Nov 24, 2014 at 2:51 PM, kevinb7 notifications@github.com wrote:

Great library. "compose" would be good. Maybe "pipe" too so that you send values through a series of functions.

Reply to this email directly or view it on GitHub:
#16

@kevinbarabash
Copy link
Author

Maybe "pipe" isn't the right word for what I was thinking of... if you can "compose" two functions, it might be useful to compose an array of functions into a single function, e.g.
compose_in_series(a, b, c) === compose(a,compose(b,c))
where
compose(a,b)(arg) === a(b(arg))
This is probably already be achievable using reduce in a creative way so maybe compose_in_series isn't super useful.

@tjmehta
Copy link
Owner

tjmehta commented Nov 24, 2014

Yup, I believe reduceRight in combination with a  compose does what you want


Sent from Mailbox

On Mon, Nov 24, 2014 at 3:30 PM, kevinb7 notifications@github.com wrote:

Maybe "pipe" isn't the right word for what I was thinking of... if you can "compose" two functions, it might be useful to compose an array of functions into a single function, e.g.
compose_in_series(a, b, c) === compose(a,compose(b,c))
where
compose(a,b)(arg) === a(b(arg))

This is probably already be achievable using reduce in a creative way so maybe compose_in_series isn't super useful.

Reply to this email directly or view it on GitHub:
#16 (comment)

@jfsiii
Copy link
Contributor

jfsiii commented Nov 25, 2014

Some quick thoughts on compose, pipe and flip
I'd like compose to accept multiple parameters, so given these values:

function times5(x) { return x * 5 }
function plus2(x) { return x + 2 }
function square(x) { return x * x }

var nums = [1,3,5,6,7];

the results of these two operations are identical

nums.map(square).map(plus2).map(times5);  // [15, 55, 135, 190, 255]
nums.map(compose(times5, plus2, square)); // [15, 55, 135, 190, 255]

compose is great, but the right-to-left composition order is less clear for many. pipe, or composeLeft, would allow the functions to be listed left-to-right, so these 3 statements are equivalent:

nums.map(square).map(plus2).map(times5);  // [15, 55, 135, 190, 255]
nums.map(compose(times5, plus2, square)); // [15, 55, 135, 190, 255]
nums.map(pipe(square, plus2, times5));    // [15, 55, 135, 190, 255]

compose

function compose() {
  var fns = arguments;
  return function () {
    var args = arguments;
    var len = fns.length;
    while (len--) args = [fns[len].apply(this, args)];
    return args[0];
  };
}

composeLeft (or pipe, or chain, or waterfall, etc)

function composeLeft() {
  var fns = [].slice.call(arguments).reverse();
  return function () {
    var args = arguments;
    var len = fns.length;
    while (len--) args = [fns[len].apply(this, args)];
    return args[0];
  };
}

which is identical to compose except for the first line. If we add another function, flip/reverseArguments, we can create composeLeft from compose via: var pipe = flip(compose)

flip (or reverseArguments, etc)

function flip(fn) {
  return function() {
    var args = [].slice.call(arguments).reverse();
    return fn.apply(this, args);
  };
}

It might seem silly to have a function which just reverses argument order, but it's a great help when you want to invoke a existing function which does what you want/need but has arguments in the opposite order.

@JakeChampion
Copy link

@jfsiii Why would you reverse the arguments in composeLeft and then loop over them backwards? Would the source code not be easier to follow if instead we cast the arguments into an array and then loop over the array forwards?

function composeLeft() {
  var fns = Array.prototype.slice.call(arguments);
  return function () {
    var args = arguments;
    fns.forEach(function(fn){
      args = [fn.apply(this, args)];
    });
    return args[0];
  };
}

I also have a question I hope could be answered. What is the benefit/purpose of applying the context this to the function?

@jfsiii
Copy link
Contributor

jfsiii commented Nov 26, 2014

@JakeChampion The functions were just examples to demonstrate their usefulness. The specific implementation can be discussed in a PR or elsewhere.

You're right that I could have omitted the argument reversal and just changed the order the functions were applied. However, that still means compose and composeLeft differ by only one line. That's why I suggested flip which would allow composeLeft to be constructed merely by var composeLeft = flip(compose).

I don't know the root of your last question. Do you think a value other than this should be supplied? null, perhaps? If so, as I said, I figured that would be discussed later. If you're interested in learning more about apply, check out these docs on MDN

@tjmehta
Copy link
Owner

tjmehta commented Nov 26, 2014

These look like good suggestions guys! Thanks will definitely be adding compose. Probably do not need composeLeft since ES5 has both a reduce and reduceRight. Will definitely look into the others soon, thanks @jfsiii!

@jfsiii
Copy link
Contributor

jfsiii commented Nov 26, 2014

@tjmehta composeLeft / pipe / chain is just compose with a different order. In my experience, people expect left-to-right, unless they know compose from Lambda calculus.

I'm glad to submit a PR. Patches welcome? :)

@tjmehta
Copy link
Owner

tjmehta commented Nov 26, 2014

I see, what I meant is that compose should only need to take two arguments - which you'd be able to combine with reduce or reduceRight eliminating the need for a composeLeft. Patches are welcome :) - 100% test coverage please.

@tjmehta
Copy link
Owner

tjmehta commented Dec 2, 2014

Added #30

@tjmehta tjmehta closed this as completed Dec 2, 2014
@kevinbarabash
Copy link
Author

@jfsiii @tjmehta awesome, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants