ghci> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
How come the second argument is (a -> m b) instead of (m a -> m b) or even (a -> b)? What is it conceptually about Monads that requires this signature? Would it make sense to have type classes with the alternative signatures t a -> (t a -> t b) -> t b resp. t a -> (a -> b) -> t b?
A more symmetric definition of a monad is the Kleisli combinator, which is basically (.) for monads:
(>=>) :: (a -> m b) -> (b -> m c) -> (a -> m c)
It can replace (>>=) in the monad's definition:
f >=> g = \a -> f a >>= g
a >>= f = const a >=> f $ ()
It is usual in Haskell to define Monad in terms of return and (>>=):
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
return :: a -> m a
However, we could instead use this equivalent definition, which is closer to the original mathematical one:
class Monad m where
fmap :: (a -> b) -> m a -> m b
join :: m (m a) -> m a
return :: a -> m a
As you can see, the asymmetry of (>>=) has been replaced with the asymmetry of join, which takes m (m a) and “squishes” the two layers of m into just m a.
You can also see that the signature of fmap matches your t a -> (a -> b) -> t b, but with the parameters reversed. This is the operation that characterises the typeclass Functor, which is strictly weaker than Monad: every monad can be made a functor, but not every functor can be made a monad.
What does this all mean in practice? Well, when transforming something that is only a functor, you can use fmap to transform the values “within” the functor, but those values can never influence the “structure” or “effect” of the functor itself. With a monad, however, that restriction is lifted.
As a concrete example, when you do fmap f [1, 2, 3], you know that no matter what f does, the resulting list will have three elements. However, when you do [1, 2, 3] >>= g, it is possible for g to transform each of those three numbers into a list containing any number of values.
Similarly, if I do fmap f readLn, I know that it can't perform any I/O actions other than reading a line. If I do readLn >>= g, on the other hand, it's possible for g to inspect the value that was read and then use that to decide whether to print out a message, or read n more lines, or do anything else that is possible within IO.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With