Skip to content

Comment on Monads in pictures

Comments

from a non-Haskell programmer here. It looks like monads are just a way to call functions in difference context, in js

  map(myObj, someFunc, contextObj)
Or am I missing something here?

It's a bit more powerful than that - consider what would happen if, every time you sequence two statements together...

  doThingOne(); doThingTwo()
... you get to pick what happens during the semicolon depending on which Monad you happen to be working with.

For example, like praptak mentioned, maybe during every semicolon you can evaluate the failure state of your program and decide not to continue. I.E., "Maybe" the execution worked, and "Maybe" I have a valid computation at this point, but "Maybe" not!

The power here is that while you are really operating in this context with an implicit possibility of having failure, you can program as if each step succeeded. So now we can string together a whole sequence of possibly-failing computations, and create a bigger possibly-failing super-computation that error checks under the hood.

ok, gotcha that is a pretty nifty feature :)

The context isn't a property of the monad abstraction, but of a concrete monad, like the State monad.

If you desugar everything from the State monad, than you can see, that the state 'c' is an explicit argument, which is hidden in the "sugared" version.

    import Control.Monad.State.Lazy
    
    type Counter = State Int
    
    -- using the State monad and the do notation
    increment :: Counter ()
    increment = do
       c <- get
       put $ c + 1
    
    -- using the State monad and desugar the do notation
    increment_ :: Counter ()
    increment_ = get >>= \c -> put $ c + 1
    
    -- desugar 'get'
    get_ = \c -> (c, c)
    
    -- desugar 'put'
    put_ c = \_ -> ((), c)
    
    -- desugar >>=
    bind_ a b = \c -> let (result, c') = a c in (b result) c' 
    
    -- "desugar" the State monad
    increment__ = get_ `bind_` \c -> put_ $ c + 1

I may be misunderstanding you, but from your description, that more describes functors than it does monads. The difference being that in a monad, the `someFunc` you pass in is responsible for creating a new context (of the same monad type).

Pretending that javascript is immutable, I think it would probably look something more like

    contextObj = monadBox(myObj)
    // someContextReturningFunc gets myObj as its argument from contextObj and generates newContextObj
    newContextObj = monadBind(contextObj, someContextReturningFunc)
    newNewContextObj = monadBind(newContextObj, someOtherContextReturningFunc)
    ...
To make this more concrete, let's take the Maybe monad/functor in javascript and compare them. The Maybe monad is described by johnpmayer elsewhere here.
    // Creating maybe "contexts"
    maybeNothing = {maybe: "nothing"}; // maybeMonad holding nothing
    maybeJust = function(x) { return {maybe: "just", just: x} }; // create a maybeMonad holding a (the '[a]' in the picture)

    // Monadic bind
    function maybeBind(maybeMonad, f) {
      // maybeMonad is '[a]' in the picture
      if (maybeMonad.maybe === "just") {
        return f(maybeMonad.just); // expects f to return another maybeMonad '[b]'
      } else {
        return maybeNothing; // also '[b]' but the b value doesn't really exist
      }
    }

    // Functor map
    function maybeMap(maybeMonad, f) {
      // maybeMonad is '[a]' in the picture
      if (maybeMonad.maybe === "just") {
        return maybeJust(f(maybeMonad.just)); // expects f to return a value without any maybe stuff ('a -> b')
        // notice that the monad wrapping remains separate from f
      } else {
        return maybeNothing;
      }
    }

    // Functor examples
    //////

    // possible values of 'f', note that maybe is not involved at all
    function addOne(x) { return x + 1; }

    // usage
    console.log(maybeMap(maybeJust(1), addOne)); // returns maybeJust(2)
    console.log(maybeMap(maybeNothing, addOne)); // returns maybeNothing


    // Monad examples
    //////
    
    // possible values for 'f', notice x is what was contained in the maybe (not the maybe itself) and the return is a maybe
    function maybeAddOneIfOdd(x) { return (x % 2 == 0) ? maybeNothing : maybeJust(x + 1); };
    function maybeAddThree(x) { return maybeJust(x + 3); };   

    // usage
    console.log(maybeBind(maybeJust(1), maybeAddThree)); // returns maybeJust(4)
    console.log(maybeBind(maybeJust(1), maybeAddOneIfOdd)); // returns maybeJust(2)
    console.log(maybeBind(maybeBind(maybeJust(1), maybeAddThree), maybeAddOneIfOdd)); // returns maybeNothing (due to 4 being even)
    console.log(maybeBind(maybeBind(maybeJust(2), maybeAddOneIfOdd), maybeAddThree)); // returns maybeNothing because the first bind returned nothing

    // and of course, composition
    function maybeMultiplyThree(x) { return maybeJust(x * 3); }
    function maybeMultiplyThreeThenAddOneIfOdd(x) {
      return maybeBind(maybeMultiplyThree(x), maybeAddOneIfOdd);
    }

    console.log(maybeBind(maybeJust(1), maybeMultiplyThreeThenAddOneIfOdd)); // returns maybeJust(4)
    console.log(maybeBind(maybeJust(2), maybeMultiplyThreeThenAddOneIfOdd)); // returns maybeNothing
Edit: added some extra examples and comments

There's more to monads. One important feature is separation of composition from execution. Second one is that the composition can do some extra things automatically, for example the Maybe monad composes functions in such a way that if the first function produces 'Nothing', the second will not be evaluated.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.