Search code examples
javascriptfunctional-programmingecmascript-6composition

How do I read functional composition in es6/javascript?


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?

Reference:

const compose = (f, g) => (a) => f(g(a)) // Definition
const floorAndToString = compose((val) => val.toString(), Math.floor) // Usage

floorAndToString(121.212121) // '121'

Solution

  • 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)).