Skip to content

Instantly share code, notes, and snippets.

@neftaly
Last active December 5, 2021 14:24
Show Gist options
  • Select an option

  • Save neftaly/6e11268f1cd230094c81 to your computer and use it in GitHub Desktop.

Select an option

Save neftaly/6e11268f1cd230094c81 to your computer and use it in GitHub Desktop.
ES6 Compose
const compose = (...args) => initial => args.reduceRight(
(result, fn) => fn(result),
initial
);
@meChrisReed
Copy link
Copy Markdown

This page was the first to come up when searching for "es6 compose".
Looking at the example code I think an improvement could be made.
The existing code:

const compose = (...args) => initial => args.reduceRight(
    (result, fn) => fn(result),
    initial
);

Does not account for the first use.

compose( words(testStr) );

Will Incorrectly return a function expecting "inital".
I propose a simplification:

const compose = (...args) => args.reduceRight((x,fn) => fn(x));

Not only have we reduced (<- pun) the function, but we now will get our initial value back on the first call.
Thoughts?

@neftaly
Copy link
Copy Markdown
Author

neftaly commented Jun 21, 2016

Hey, I didn't see your comment earlier, sorry. I like your simplification, but this is actually intended behaviour, and operates the same way as Ramda.

The idea is that a composition will always return a fn. This isn't an artificial stylistic rule, but a behaviour described by the type signature I haven't bothered to write 😄

While less elegant, an extension of the idea allows multiple args to be provided to the first fn (like in Ramda).

const compose = ([ firstArg, ...restArgs ]) => (...initial) => restArgs.reduceRight(
    (result, fn) => fn(result),
    firstArg(...initial)
);
// Same thing, written differently
const compose = (...args) => (...initial) => args.reduceRight(
    (result, fn) => [ fn(...result) ],
    initial
)[0];

const example = compose(
    z => z.toUpperCase(),
    (a, b) => a + " " + b
)("foo", "bar");

That said, your way is perfectly valid if you're just borrowing ideas from functional JS programming and haven't fully bought into the whole thing.

@neftaly
Copy link
Copy Markdown
Author

neftaly commented Jun 21, 2016

Type signature is something like compose :: (c, b, a, ...) -> (x, y, z, ...) -> ...(c(b(a(x, y, z, ...))))

@iamstarkov
Copy link
Copy Markdown

iamstarkov commented Aug 24, 2016

@neftaly

const compose = ([ firstArg, ...restArgs ]) => (...initial) => restArgs.reduceRight(
    (result, fn) => fn(result),
    firstArg(...initial)
);

this implementation is broken in sense that for compose right most function can be any arity, and in this example its left most


// Same thing, written differently
const compose = (...args) => (...initial) => args.reduceRight(
    (result, fn) => [ fn(...result) ],
    initial
)[0];

this implementation will be broken if any function inside compose will return array as a value, so next function will take this array not as first argument, but as arguments as a whole

@iamstarkov
Copy link
Copy Markdown

iamstarkov commented Aug 24, 2016

@neftaly @meChrisReed

Working as intended implementation should look like this:

const compose = (...fns) => {
  const [tailFn, ...restFns] = fns.reverse();
  return (...args) => restFns.reduce(
    (value, fn) => fn(value),
    tailFn(...args)
  );
};

const pipe = (...fns) => compose(...fns.reverse());

Or other way around

const pipe = (headFn, ...restFns) => (...args) => restFns.reduce(
  (value, fn) => fn(value),
  headFn(...args)
);

const compose = (...fns) => pipe(...fns.reverse());

@weiying-chen
Copy link
Copy Markdown

@iamstarkov How does the functions handle functions with two arguments? Could you give me an example or how to modify the code so it handles them?

@WaldoJeffers
Copy link
Copy Markdown

Hello, here's my version https://gist.github.com/WaldoJeffers/905e14d03f4283599bac753f73b7716b. Does it cover your use cases ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment