Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asymmetry in the bind function

Tags:

haskell

monads

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?

like image 200
foobar Avatar asked Sep 06 '11 11:09

foobar


2 Answers

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 $ ()
like image 115
fuz Avatar answered Sep 19 '22 16:09

fuz


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.

like image 43
Stuart Cook Avatar answered Sep 20 '22 16:09

Stuart Cook