Alex V
Alex V

Reputation: 3644

Javascript closure returning string instead of function

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!

Upvotes: 1

Views: 213

Answers (3)

Nina Scholz
Nina Scholz

Reputation: 386560

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));

Upvotes: 1

Jonas Wilms
Jonas Wilms

Reputation: 138257

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);

Upvotes: 2

Tom Wiesing
Tom Wiesing

Reputation: 156

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.

Upvotes: 3

Related Questions