Search code examples
haskellmonads

Understanding bind function in Haskell


I am familiar with monads in category theory (they are a very easy concept, in fact), yet >>= function in Haskell completely puzzles me. Ok, so applying bind to a value of M a and a function a -> M u is the same as first applying the monad to this function, then evaluating it at the specified value and multiplying the result: a >>= f is the same as join $ (fmap f) $ a. But how is this a natural description of computation? Is there some useful way of looking at it that would help me understand it?

Is there some nice article somewhere that is not geared towards someone fresh from the C++ jungle?


Solution

  • Consider the monadic function composition operator <=<. This is analogous to . except it works on monadic functions. It can be defined simply in terms of >>=, so learning about one will educate us about the other.

    (<=<) :: (b -> m c) -> (a -> m b) -> a -> m c
    (f <=< g) x =  g x >>= f
    
    (.) :: (b -> c) -> (a -> b) -> a -> c
    (f . g) x = g x |> f
      where z |> h = h z
    

    In the case of ., g is "performed" first, and then f is performed on the output of g. In the case of <=<, g and its effects are "performed" first, and then f and its effects are performed. It's a bit of a misnomer to say that one happens "before" the other, actually, since not all monads work that way.

    Perhaps it is more accurate to say that f can take advantage of additional contextual information provided by g. But that's not entirely correct, since g could potentially take away contextual information. If you want to 100% correctly describe monads, you really have to walk on eggshells.

    But in almost all nontrivial cases, f <=< g means that the effects (as well as the result) of the monadic function g will subsequently influence the behavior of the monadic function f.


    To address questions about v >>= f = join (fmap f v)

    Consider f :: a -> m b and v :: m a. What does it mean to fmap f v? Well fmap :: (c -> d) -> m c -> m d, and in this case c = a and d = m b, so fmap f :: m a -> m (m b). Now, of course, we can apply v :: m a to this function, resulting in m (m b). but what exactly does that result type m (m b) mean?

    The inner m represents the context from produced from f. The outer m represents the context originating from v (n.b. fmap should not disturb this original context).

    And then you join that m (m b), smashing those two contexts together into m a. This is the heart of the definition of Monad: you must provide a way to smash contexts together. You can inspect the implementation details of various Monad instances to try and understand how they "smash" contexts together. The takeaway here, though, is that the "inner context" is unobservable until you merge it with the "outer context". If you use v >>= f, then there is no actual notion of the function f receiving a pure value a and producing a simple monadic result m b. Instead, we understand that f acts inside the original context of v.