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.
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
You can search for your operator on Hoogle, and see that it is called (>=>)
. Its definition in terms of (>>=)
is quite simple:
f >=> g = \x -> f x >>= g
In some sense (>=>)
is better reflective of the idea to generalize composition, but I think (>>=)
works better as a primitive operator simply because it's practical in more cases, and easier to relate to do-notation.
(>>=)
is not a composition operator. It's an application operator.
(&) :: a -> (a -> b) -> b
(>>=) :: Monad m => m a -> (a -> m b) -> m b
There's also (=<<)
(from Control.Monad
), which corresponds to the more usual application operator ($)
:
($) :: (a -> b) -> a -> b
(=<<) :: Monad m => (a -> m b) -> m a -> m b
For composition, we have both (<=<)
and (>=>)
(again from Control.Monad
, the first being exactly analogous to (.)
:
(.) :: (b -> c) -> (a -> b) -> a -> c
(<=<) :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
((>=>)
is just (<=<)
with its arguments flipped; (>=>) = flip (<=<)
)
While we are comparing types, you might want to look at how fmap
fits in.
($) :: (a -> b) -> a -> b
fmap :: Functor f => (a -> b) -> f a -> f b
(=<<) :: Monad m => (a -> m b) -> m a -> m b
($)
and fmap
take the same type of function, but apply it to different types of argument.
fmap
and (=<<)
take different types of functions, but apply them both to the same type of argument (though in different ways).
I agree that thinking in terms of ( >=> ) :: ( a -> m b ) -> ( b -> m c ) -> ( a -> m c)
often feels more natural as it is closer to usual function composition and in fact it is composition in the Kleisli category. Many of Haskell's monad instances are actually easier to understand when looking at them from this point of view.
One reason why Haskell chose ( >>= ) :: m a -> ( a -> m b) -> m b
might be that this definition is in a way the most universal one. Both >=>
and join :: m ( m x ) -> m x
can be reduced to >>=
:
( >=> ) f g x = f x >>= g
join mmx = mmx >>= id
If you add return :: x -> m x
to the mix it also possible to derive fmap :: ( a -> b ) -> m a -> m b
(Functor) and ( <*> ) :: m ( a -> b ) -> m a -> m b
(Applicative):
fmap f ma = ma >>= ( return . f )
( <*> ) mab ma =
mab >>= \f ->
ma >>= \a ->
return ( f a )