Reputation: 527
I wanted to create my own implementation of compose
This works
const compose = (...fns) => fns.reduce((f,g) => (...args) => f(g(...args)));
This not
const compose1 = (...fns) => (...args) => fns.reduce((f,g) => f(g(...args)));
.
const multi = a => x => x*a;
const devide = a => x => x/a;
const add = a => x => x+a;
compose(add(6), multi(4), devide(5))(5); // 10 as expected
compose1(add(6), multi(4), devide(5))(5); // Uncaught TypeError: f is not a function
Could you explain me why second implementation doesn't work?
Upvotes: 2
Views: 197
Reputation: 4646
const compose = (...fns) => {
console.log('function', fns);
return fns.reduce((f,g) => {
console.log('f and g' , f,g)
return (...args) => {
console.log('args is ' , args)
return f(g(...args));
}
})
};
// call with
compose(add(6), multi(4), devide(5))(5);
will yield
VM1239:2 function (3) [ƒ, ƒ, ƒ]
VM1239:4 f and g x => x+a x => x*a
VM1239:4 f and g (...args) => {
console.log('args is ' , args)
return f(g(...args));
} x => x/a
VM1239:6 args is [5]
VM1239:6 args is [1]
10
Which works all fine
do the same with your compose1
const compose1 = (...fns) => {
console.log('functions ', fns)
return (...args) => {
console.log('args', args)
return fns.reduce((f,g) => {
console.log('f and g', f,g)
return f(g(...args))
})
}
};
//called with
compose1(add(6), multi(4), devide(5))(5)
will yield
VM1783:2 functions (3) [ƒ, ƒ, ƒ]
0: x => x+a
1: x => x*a
2: x => x/a
VM1783:4 args [5]
VM1783:6 f and g x => x+a x => x*a
VM1783:6 f and g 26 x => x/a
VM1783:7 Uncaught TypeError: f is not a function
at fns.reduce (<anonymous>:7:14)
at Array.reduce (<anonymous>)
at args (<anonymous>:5:16)
at <anonymous>:1:38
so your f
is now 26 which is not a function :)
Explanation:
you called
f (g (...args))
f ( g ( 5 ) )
f ( 5 * a) // a is 4
f ( 20 )
16 + a // a is 6
26
Upvotes: 2
Reputation: 1949
Let's take a look at your compose1
by breaking it down:
const compose1 = (...fns) => {
// Returns a function that reduces fns each time it is called
return (...args) => {
// Returns the reduction of fns
return fns.reduce((f,g) => {
// Returns the result of calling f(g(...args))
return f(g(...args))
})
}
};
Now lets go through a sample call of this version of compose:
compose1(add(6), multi(4), devide(5))(5);
First, we call compose1
. This returns this function:
(...args) => {
// Returns the reduction of fns
return fns.reduce((f,g) => {
// Returns the result of calling f(g(...args))
return f(g(...args))
})
}
We then call this function with a parameter of 5
, which is essentially calling fns.reduce
as in the previously returned function. To see what exactly is going wrong, let's run through a few iterations of the reduce:
First iteration:
fns.reduce((f = add(6), g = multi(4)) => {
// Returns the result of calling f(g(...args))
return f(g(...args = [5]))
// g is `multi(4)`, so do `multi(4)(5)`, which returns 20. Next:
// f is `add(6)`, so do `add(6)(20)`, which returns 26.
// Remember that the return from last iteration is
// the first parameter of the next iteration
})
Second iteration:
fns.reduce((f = 26, g = devide(5)) => {
// Returns the result of calling f(g(...args))
return f(g(...args = [5]))
// g is `devide(5)`, so do `devide(5)(5)`, which returns 1. Next:
// f is 26, so do `26(1)`. However, 26 is not a function.
// You get the error `f is not a function`.
})
Upvotes: 1
Reputation: 43186
This is what you needed to to do make your version work:
const compose1 = (...fns) => (...args) => fns.reduceRight((res, f) => f(res), fns.pop()(...args))
The reason why yours failed is because reduce
(and reduceRight
) takes a function with this signature:
(previousValue, currentValue) => newPreviousValue
What happens in your version is that after the first time, reduce calls your function, newPreviousValue
is now no longer a function, it is now f(g(...args))
(the result of the last function call), so when reduce tries to "call" that function again, it uses the result of the last call as if it's a function, so you get the error.
If you see the one I have above, res
is the previousValue
, which is the result of the last function call, but it is not being used as a function.
If you are wondering how come it even succeeded on the first call, well the reason for this is because you did not supply an initial value for reduce
, so it simply took the first two functions from the list as the parameters for the function, which allowed you to do: f(g(...args))
Upvotes: 2
Reputation: 3368
By expanding the syntax out into multiple lines, you can start to see where the problems are in the second function. You're callings fns.reduce
where you should be calling args.reduce
. From there, it still returns a different value so there's most likely some other issues going on, but that's a start.
The console logs in the second should also show that the value of f
isn't what you're expecting it to be.
Ideally you should only write shorthand for very simple operations, or for things you've already fully tested out in a manner easier to read and test against.
const compose = (...fns) => {
return fns.reduce((f,g) => {
console.log('f: ',f,'g: ',g)
return (...args) => f(g(...args))
})
};
const compose1 = (...fns) => {
return (...args) => {
return args.reduce((f,g) => { //was fns.reduce
console.log('f: ',f,'g: ',g)
return f(g(...args))
})
}
}
const multi = a => x => x*a;
const devide = a => x => x/a;
const add = a => x => x+a;
var result1 = compose(add(6), multi(4), devide(5))(5); // 10 as expected
console.log(result1)
var result2 = compose1(add(6), multi(4), devide(5))(5); // Uncaught TypeError: f is not a function
console.log(result2)
Upvotes: 1