Search code examples
javascriptfunctional-programmingmonads

Difficulty understanding functional programming composition, functors and monads example


I've kind of grasped some knowledge on functional programming but can't really wrap my head around this function programming block of code. I didn't really knew where I should ask something like this I couldn't figure out so asked it here. So I would really appreciate if someone will help me understand what this higher order function, or a monads example doing?

P.S this code is from composing software book by Eric Elliot

const f = n => n+1;
const g = n => n*2;

This composeM functions is made to compose and map or over numbers of functions? I know reduce but really have no idea how this function should be working.

const composeM = (...mps) => mps.reduce((f, g) => x => g(x).map(f));
const h = composeM(f,g);
h(20)

Then, the function composeM was more generalized by doing:

const compose = methods => (...mps) => mps.reduce((f, g) => x => g(x)[method](f));

Then, I could create composedPromises or composedMaps like

const composePromises = compose("then")(f,g);

How is the g(x)[method](f) even working? it should be g(x).then(f).

Update above map composeM function not working

const f = n => Promise.resolve( n+1 );
const g = n => Promise.resolve( n*2 );

const composePromises = (...mps) => mps.reduce((f, g) => x => g(x).then(f))
const h = composePromises(f, g)
h(20)

Solution

  • Consider function composition, which has the following type signature.

    // compose :: (b -> c) -- The 1st argument is a function from b to c.
    //         -> (a -> b) -- The 2nd argument is a function from a to b.
    //         -> (a -> c) -- The final result is a function from a to c.
    
    //               +-----------------b -> c
    //               |  +---------a -> b
    //               |  |     +-- a
    //               |  |     |
    const compose = (f, g) => x => f(g(x));
    //                             |_____|
    //                                |
    //                                c
    

    The composeP function is similar to the compose function, except it composes functions that return promises.

    // composeP :: (b -> Promise c) -- The 1st argument is a function from b to promise of c.
    //          -> (a -> Promise b) -- The 2nd argument is a function from a to promise of b.
    //          -> (a -> Promise c) -- The final result is a function from a to promise of c.
    
    //                +-------------------------b -> Promise c
    //                |  +-------- a -> Promise b
    //                |  |     +-- a
    //                |  |     |
    const composeP = (f, g) => x => g(x).then(f);
    //                              |__________|
    //                                   |
    //                               Promise c
    

    Remember that the then method applies the callback function to the value of the promise. If we replace .then with [method] where method is the name of the bind function of a specific monad, then we can compose functions that produces values of that monad.

    For example, .flatMap is the bind function of arrays. Hence, we can compose functions that return arrays as follows.

    // composeA :: (b -> Array c) -- The 1st argument is a function from b to array of c.
    //          -> (a -> Array b) -- The 2nd argument is a function from a to array of b.
    //          -> (a -> Array c) -- The final result is a function from a to array of c.
    
    const composeA = (f, g) => x => g(x).flatMap(f);
    
    // f :: Bool -> Array String
    const f = x => x ? ["yes"] : ["no"];
    
    // g :: Int -> Array String
    const g = n => [n <= 0, n >= 0];
    
    // h :: Int -> Array String
    const h = composeA(f, g);
    
    console.log(h(-1)); // ["yes", "no"]
    console.log(h(0));  // ["yes", "yes"]
    console.log(h(1));  // ["no",  "yes"]

    That was a very contrived example but it got the point across.

    Anyway, the generic compose function composes monads.

    const compose = method => (f, g) => x => g(x)[method](f);
    
    const composeP = compose("then");
    const composeA = compose("flatMap");
    

    Finally, the various monad compose functions only compose two functions at a time. We can use reduce to compose several of them at once. Hope that helps.