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