1

I am trying to write a simple compose function that takes a series of functions, and composes them like so:

compose(func1, func2, func3)(n) === func1(func2(func3(n)))

I do so by recursing through a rest parameter,

var compose = function(...args) {
    return (n) => {
        if (args.length) {
            return args[0](compose(args.slice(1)));
        } else {
            return n;
        }
    };
};

I then attempt to compose a new function made up of a series of other functions,

var plusOneAndTwo = compose((n) => n + 1, (n) => n + 2);
plusOneAndTwo(1);

Instead of returning 4, I get back the body of the inner, anonymous function inside of compose as a string,

"(n) => {
        if (args.length) {
            return args[0](compose(args.slice(1)));
        } else {
            return n;
        }
    }1"

Note the "1" at the end of the string! I have no idea why this is happening, and in particular I'm perplexed by how a 1 is getting appended to the end of that.

Any clarification is appreciated, thanks!

5
  • 3
    compose returns a function, yes. You're passing the return value of compose, which is a function, to args[0](). I don't think it's a good idea to use compose recursively. I'd simply .reduce the args instead. Commented May 8, 2019 at 15:06
  • @deceze I expect compose to return a function. But when I call the function returned by compose, I get it's body as a string. That's what I'm confused about. edit: Ah okay, I see what you mean. Thanks! Commented May 8, 2019 at 15:07
  • compose(args.slice(1)) You don't actually call the function returned by compose here as you do in the initial call, you use the function as the argument to args[0] instead of the result of calling it. Commented May 8, 2019 at 15:09
  • 1
    No, the crux is at args[0](compose(...)). This passes a function to (n) => n + 1, which concatenates 1 to the function body. Commented May 8, 2019 at 15:09
  • @deceze Thanks a lot, that makes a ton of sense, mystery explained! Commented May 8, 2019 at 15:12

3 Answers 3

3

The problem occurs in the recursive call to compose. In particular, you are not passing the parameter n to it (as also suggested by others above). Furthermore, you need to expand the rest parameter in the call.

You should use something like:

return args[0](compose(...args.slice(1))(n))

In your case, you are simply returning:

return args[0](compose(args.slice(1)));

In your example you call compose((n) => n + 1, (n) => n + 2);. Compose then returns a function taking n as a parameter. In this function, args.length becomes 1 (i.e. true-is), args[0] becomes (n) => n + 1 and args.slice(1) becomes [(n) => n + 2].

Next, you call this returned function with the parameter n = 1. As args.length is 1, the if() statement will go into the if() case. In this if case, it will call args[0] with the argument compose(args.slice(1)).

In this recursive call, compose(args.slice(1)) is evaluated to a function, taking n as a parameter and the same function body. This function is then given as the parameter n to args[0] (in the outer call). Recall that args[0] in this scenario is the function (n) => n + 1.

Thus the call as a whole is equivalent to:

// returned from the recursive call to compose(args.slice(1))
var n = (n) => {
        if (args.length) {
            return args[0](compose(args.slice(1)));
        } else {
            return n;
        }
}
// applied to arg[0] == (n) => n + 1
return n + 1

This means that the code will attempt to add a function with the number 1. In JavaScript adding a function and a number results in both objects coerced into a string. Casting a number into a string is trivial, casting a function into a string returns the function source code. These strings are then added to give the return value you saw: The function body as a string with the 1 at the end.

Sign up to request clarification or add additional context in comments.

Comments

2

You just have to call the composed function:

 return args[0](compose(...args.slice(1))(n));

Or without recursion it'll be:

 const compose = (...fns) => start => fns.reduceRight((acc, fn) => fn(acc), start);

Comments

1

You could take a different approach by returning a new function or returning the last function for calling with arguments.

const
    compose = (...fns) => fns.length
        ? v => compose(...fns.slice(0, -1))(fns.pop()(v))
        : v => v,
    fn1 = n => n * 5,
    fn2 = n => n + 2,
    fn3 = n => n * 7;

console.log(fn1(fn2(fn3(1))));
console.log(compose(fn1, fn2, fn3)(1));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.