Background:
Composition is putting two functions together to form a third function where the output of one function is the input of the other.
No matter how much I look at this I struggle with how to read it. In particular why the compose()
return => (a) =>
captures the 121.2121212
in local scope. Also I struggle with how final fn f(g(a))
would look with all the values/fn present w/o the use of variables.
Question: Does anyone have any techniques or diagrams for quickly reading examples like this; how can I mentally debug and follow the function flow?
const compose = (f, g) => (a) => f(g(a)) // Definition
const floorAndToString = compose((val) => val.toString(), Math.floor) // Usage
floorAndToString(121.212121) // '121'
As mentioned by T.J. Crowder, it often helps rewriting arrow functions as regular functions. So the function:
const compose = (f, g) => (a) => f(g(a))
Can be rewritten as:
function compose (f, g) {
return function (a) {
return f(g(a));
}
}
Now it is perhaps more obvious what's going on. So now let's rewrite the other parts:
const floorAndToString = compose((val) => val.toString(), Math.floor)
Can be rewritten as:
function convertToString (val) { return val.toString() };
const floorAndToString = compose(convertToString, Math.floor);
Now it may be more obvious that the compose
function will return the function:
// remember that we pass `convertToString` as `f`
// and `Math.floor` as `g`:
function (a) {
return convertToString(Math.floor(a));
}
So it's obvious that the function floorAndToString
simply returns the result of convertToString(Math.floor(a))
. There is nothing special about compose
that captures 121.2121212
because it doesn't. Instead it creates a function where 121.2121212
can be passed as an argument to convertToString(Math.floor(a))
.