A key ability of Monad
is to "look inside" the m a
type and see an a
; but a key restriction of Monad
is that it must be possible for monads to be "inescapable," i.e., the Monad
typeclass operations should not be sufficient to write a function of type Monad m => m a -> a
. (>>=) :: Monad m => m a -> (a -> m b) -> m b
gives you exactly this ability.
But there's more than one way to achieve that. The Monad
class could be defined like this:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Functor f => Monad m where
return :: a -> m a
join :: m (m a) -> m a
You ask why could we not have a Monad m => m a -> (m a -> m b) -> m b
function. Well, given f :: a -> b
, fmap f :: ma -> mb
is basically that. But fmap
by itself doesn't give you the ability to "look inside" a Monad m => m a
yet not be able to escape from it. However join
and fmap
together give you that ability. (>>=)
can be written generically with fmap
and join
:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
ma >>= f = join (fmap f ma)
In fact this is a common trick for defining a Monad
instance when you're having trouble coming up with a definition for (>>=)
—write the join
function for your would-be monad, then use the generic definition of (>>=)
.
Well, that answers the "does it have to be the way it is" part of the question with a "no." But, why is it the way it is?
I can't speak for the designers of Haskell, but I like to think of it this way: in Haskell monadic programming, the basic building blocks are actions like these:
getLine :: IO String
putStrLn :: String -> IO ()
More generally, these basic building blocks have types that look like Monad m => m a
, Monad m => a -> m b
, Monad m => a -> b -> m c
, ..., Monad m => a -> b -> ... -> m z
. People informally call these actions. Monad m => m a
is a no-argument action, Monad m => a -> m b
is a one-argument action, and so on.
Well, (>>=) :: Monad m => m a -> (a -> m b) -> m b
is basically the simplest function that "connects" two actions. getLine >>= putStrLn
is the action that first executes getLine
, and then executes putStrLn
passing it the result that was obtained from executing getLine
. If you had fmap
and join
and not >>=
you'd have to write this:
join (fmap putStrLn getLine)
Even more generally, (>>=)
embodies a notion much like a "pipeline" of actions, and as such is the more useful operator for using monads as a kind of programming language.
Final thing: make sure you are aware of the Control.Monad
module. While return
and (>>=)
are the basic functions for monads, there's endless other more high-level functions that you can define using those two, and that module gathers a few dozen of the more common ones. Your code should not be forced into a straitjacket by (>>=)
; it's a crucial building block that's useful both on its own and as a component for larger building blocks.
flip id
has (among others) the typeMonad m => m a -> (m a -> m b) -> m b
. – Daniel Wagner