This is the code:
const Pipe = (...fns) => fns.reduce((f,g) => (...args) => g(f(...args)));
So by (...fns) the fns arguments beco
For better understanding I have translated the code to block level as below trying to explain the code in question for a novice developer which helps. For better implementation @naomik has solution.
ES6
const Pipe = (...fns) => {
return fns.reduce((f, g) => {
return (...args) => {
return g(f(...args))
}
})
};
Equivalent ES5 implementation:
var Pipe = function () {
var fns = [];
for (var _i = 0; _i < arguments.length; _i++) {
fns[_i] = arguments[_i];
}
return fns.reduce(function (f, g) {
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return g(f.apply(void 0/*Undefined or can use 'null'*/, args));
};
});
};
Part by part breakdown:(read the code comments for better understanding)
const inc = (num) => num+1 //Function to increment the number
const dbl = (num) => num*2 //Function double's the number
const sqr = (num) => num*num //Function Square's the number
/*Function breakdown*/
const _pipe = (f, g) => (...args) => g(f(...args)); //Second part
const Pipe = (...fns) => fns.reduce(_pipe); //First part
const incDblSqr = Pipe(inc, dbl, sqr) //Piping all the functions
const result = incDblSqr(2) // Piped function
console.log(result) // ((2+1)*2) ^ 2 = 36
Explaination:
The code in question helps to pipe the result from one function to another function.
Step by step process of above code:
All these are achieved using closures which has access to the reducing arguments (Read the article linked below for clarity).
Conclusion: The code in question helps to pipe the n-number of functions that operates on the result emitted by previous function.
Best article by Andrew L. Van Slaars: https://vanslaars.io/post/create-pipe-function/
Please read the above article for clarity and also @naomik solution
Well @naomik beat me to just about the same answer but I thought I'd still share what I had to help you explain how the function works in a possibly less cryptic way. (Maybe)
I think you already know how the "...
" works (and in case you didn't then naomik's answer should help with that :D)
Here is another version of that same pipe function expect re-written to better explain what's going on using assignments just to explain the point.
Array.prototype.reduce calls the "reducer", toASingleFunction
, multiple times--one call for each function in functionsToPipe
. The currentFunctionToPipe
is first x => x + 1
then x => x * 2
and so on...
The first value of newFunction
is theIdentityFunction
and the reducer returns another function nextNewFunction
. As the name suggests, it becomes the next newFunction
in the next call to the "reducer" (toASingleFunction
).
Once all the items in functionsToPipe
have been reduced, the final newFunction
is returned as finalPipeFunction
.
/**
* `x` goes to itself
*/
const theIdentityFunction = x => x;
/**
* the `reducer` that reduces the array of functions into a single function
* using the value from the last function as the input to the next function
*/
const toASingleFunction = (newFunction, currentFunctionToPipe) => {
const nextNewFunction = function(value) { // i'm not using arrow functions here just to explicitly show that `nextNewFunction` is, in fact, a function
const valueFromLastFunction = newFunction(value);
return currentFunctionToPipe(valueFromLastFunction);
}
return nextNewFunction;
};
const pipe = (...functionsToPipe) => {
const finalPipeFunction = functionsToPipe.reduce(toASingleFunction, /* start with */ theIdentityFunction);
return finalPipeFunction;
}
const f = pipe(
x => x + 1,
x => x * 2,
x => x * x
);
console.log(f(2)) // ((2 + 1) * 2) ^ 2 === 36
Maybe this helps?
good luck!
Your first problem is you're dealing with a bad implementation of pipe
– the second problem is there's a variety of spread syntaxes in new JavaScript and it's not always clear (to beginners) which one is being used where
rest parameter
a rest parameter collects the supplied arguments to a function in an array. This replaces the old arguments
object of JavaScript's yesteryear
const f = (...xs) =>
xs
console.log(f()) // []
console.log(f(1)) // [1]
console.log(f(1,2)) // [1,2]
spread arguments
spread arguments allows you to spread an array (or any iterable) as arguments to a function call. This replaces (almost all) instances of Function.prototype.apply
const g = (a,b,c) =>
a + b + c
const args = [1,2,3]
console.log(g(...args)) // 6
why that pipe
is bad
It's not a total function – pipe
s domain is [Function]
(array of functions), but this implementation will produce an error if an empty array of functions is used (TypeError: Reduce of empty array with no initial value
)
It might not be immediately apparent how this would happen, but it could come up in a variety of ways. Most notably, when the list of functions to apply is an array that was created elsewhere in your program and ends up being empty, Pipe
fails catastrophically
const foo = Pipe()
foo(1)
// TypeError: Reduce of empty array with no initial value
const funcs = []
Pipe(...funcs) (1)
// TypeError: Reduce of empty array with no initial value
Pipe.apply(null, funcs) (1)
// TypeError: Reduce of empty array with no initial value
Pipe.call(null) (1)
// TypeError: Reduce of empty array with no initial value
reimplementing pipe
This is one of countless implementations, but it should be a lot easier to understand. We have one use of a rest parameter, and one use of a spread argument. Most importantly, pipe
always returns a function
const pipe = (f,...fs) => x =>
f === undefined ? x : pipe(...fs) (f(x))
const foo = pipe(
x => x + 1,
x => x * 2,
x => x * x,
console.log
)
foo(0) // 4
foo(1) // 16
foo(2) // 36
// empty pipe is ok
const bar = pipe()
console.log(bar(2)) // 2
"but i heard recursion is bad"
OK, so if you're going to pipe thousands of functions, you might run into a stack overflow. In such a case, you can use the stack-safe Array.prototype.reduce
(or reduceRight
) like in your original post.
This time instead of doing everything within pipe
, I'm going to decompose the problem into smaller parts. Each part has a distinct purpose, and pipe
is now only concerned with how the parts fit together.
const comp = (f,g) => x =>
f(g(x))
const identity = x =>
x
const pipe = (...fs) =>
fs.reduceRight(comp, identity)
const foo = pipe(
x => x + 1,
x => x * 2,
x => x * x,
console.log
)
foo(0) // 4
foo(1) // 16
foo(2) // 36
// empty pipe is ok
const bar = pipe()
console.log(bar(2)) // 2
"I really just want to understand the code in my post though"
OK, let's step thru your pipe
function and see what's happening. Because reduce
will call the reducing function multiple times, I'm going to use a unique renaming for args
each time
// given
const Pipe = (...fns) => fns.reduce((f,g) => (...args) => g(f(...args)));
// evaluate
Pipe(a,b,c,d)
// environment:
fns = [a,b,c,d]
// reduce iteration 1 (renamed `args` to `x`)
(...x) => b(a(...x))
// reduce iteration 2 (renamed `args` to `y`)
(...y) => c((...x) => b(a(...x))(...y))
// reduce iteration 3 (renamed `args` to `z`)
(...z) => d((...y) => c((...x) => b(a(...x))(...y))(...z))
So what happens then when that function is applied? Let's have a look when we apply the result of Pipe(a,b,c,d)
with some argument Q
// return value of Pipe(a,b,c,d) applied to `Q`
(...z) => d((...y) => c((...x) => b(a(...x))(...y))(...z)) (Q)
// substitute ...z for [Q]
d((...y) => c((...x) => b(a(...x))(...y))(...[Q]))
// spread [Q]
d((...y) => c((...x) => b(a(...x))(...y))(Q))
// substitute ...y for [Q]
d(c((...x) => b(a(...x))(...[Q]))
// spread [Q]
d(c((...x) => b(a(...x))(Q))
// substitute ...x for [Q]
d(c(b(a(...[Q])))
// spread [Q]
d(c(b(a(Q)))
So, just as we expected
// run
Pipe(a,b,c,d)(Q)
// evalutes to
d(c(b(a(Q))))
additional reading
I've done a lot of writing on the topic of function composition. I encourage you to explore some of these related questions/I've done a lot of writing on the topic of function composition. I encourage you to explore some of these related questions/answers
If anything, you'll probably see a different implementation of compose
(or pipe
, flow
, et al) in each answer. Maybe one of them will speak to your higher conscience!