Search code examples
haskellbindmonads

Why is the bind operator (>>=) defined as it is?


I have been studying Haskell for several weeks now (just for fun) and just watched Brian Beckman's great video introducing monads. He motivates monads with the need to create a more general composition operator. Following this line of thought, if I have two functions:

f :: a -> b
g :: b -> c

the composition operator should satisfy

h = g . f :: a -> c

and from this I can infer the correct type of the . operator:

(.) : (b -> c) -> (a -> b) -> (a -> c)

When it comes to monads, suppose I have two functions:

f :: a -> m b
g :: b -> m c

It seems to me that the natural choice would have been to define a generalized composition operator that works as follows:

h = f >>= g :: a -> m c

in which case the >>= operator would have a type signature of:

(>>=) :: (a -> m b) -> (b -> m c) -> (a -> m c)

But actually the operator seems to be defined so that

h a = (f a) >>= g :: m c

and thus

(>>=) : m b -> (b -> m c) -> m c

Could someone explain the reasoning behind this choice of definition for bind? I assume there is some simple connection between the two choices where one can be expressed in terms of the other, but I'm not seeing it at the moment.


Solution

  • Could someone explain the reasoning behind this choice of definition for bind?

    Sure, and it's almost exactly the same reasoning you have. It's just... we wanted a more general application operator, not a more general composition operator. If you've done much (any) point-free programming, you'll immediately recognize why: point-free programs are hard to write, and incredibly hard to read, compared to pointful ones. For example:

    h x y = f (g x y)
    

    With function application, this is completely straightforward. What's the version that only uses function composition look like?

    h = (f .) . g
    

    If you don't have to stop and stare for a minute or two the first time you see this, you might actually be a computer.

    So, for whatever reason: our brains are wired to work a bit better with names and function application out of the box. So here's what the rest of your argument looks like, but with application in place of composition. If I have a function and an argument:

    f :: a -> b
    x :: a
    

    the application operator should satisfy

    h = x & f :: b
    

    and from this I can infer the correct type of the & operator:

    (&) :: a -> (a -> b) -> b
    

    When it comes to monads, suppose my function and argument are monadic:

    f :: a -> m b
    x :: m a
    

    The natural choice is to define a generalized application operator that works as follows:

    h = x >>= f :: m b
    

    in which case the >>= operator would have a type signature of:

    (>>=) :: m a -> (a -> m b) -> m b