20

New to Haskell, and am trying to figure out this Monad thing. The monadic bind operator -- >>= -- has a very peculiar type signature:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

To simplify, let's substitute Maybe for m:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b

However, note that the definition could have been written in three different ways:

(>>=) :: Maybe a -> (Maybe a -> Maybe b) -> Maybe b
(>>=) :: Maybe a -> (      a -> Maybe b) -> Maybe b
(>>=) :: Maybe a -> (      a ->       b) -> Maybe b

Of the three the one in the centre is the most asymmetric. However, I understand that the first one is kinda meaningless if we want to avoid (what LYAH calls boilerplate code). However, of the next two, I would prefer the last one. For Maybe, this would look like:

When this is defined as:

(>>=) :: Maybe a -> (a -> b) -> Maybe b

instance Monad Maybe where
   Nothing  >>= f = Nothing
   (Just x) >>= f = return $ f x

Here, a -> b is an ordinary function. Also, I don't immediately see anything unsafe, because Nothing catches the exception before the function application, so the a -> b function will not be called unless a Just a is obtained.

So maybe there is something that isn't apparent to me which has caused the (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b definition to be preferred over the much simpler (>>=) :: Maybe a -> (a -> b) -> Maybe b definition? Is there some inherent problem associated with the (what I think is a) simpler definition?

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
ssm
  • 5,277
  • 1
  • 24
  • 42
  • You are perhaps incorrect when you say that the definition of bind could have been written differently; note that `f :: Maybe a -> (Maybe a -> Maybe b) -> Maybe b == flip ($); and `g :: Maybe a -> ( a -> b) -> Maybe b == flip fmap`. So the other functions you listed are both useful and both exist, they just live in a place other than `Monad`. – user2407038 Apr 14 '14 at 06:02
  • `Maybe a -> (a -> b) -> Maybe b` would not allow you to bind a function that returns `Maybe b`. You wouldn't be able to get a `Nothing` halfway through a calculation. – user253751 Apr 14 '14 at 10:05
  • 1
    Another possibility is `Maybe a -> Maybe (a -> b) -> Maybe b` -- which is basically `<*>` from `Applicative` (kind of "in-between" Functor and Monad). – Matt Fenwick Apr 14 '14 at 11:25

5 Answers5

25

It's much more symmetric if you think in terms the following derived function (from Control.Monad):

(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> (a -> m c)
(f >=> g) x = f x >>= g

The reason this function is significant is that it obeys three useful equations:

-- Associativity
(f >=> g) >=> h = f >=> (g >=> h)

-- Left identity
return >=> f = f

-- Right identity
f >=> return = f

These are category laws and if you translate them to use (>>=) instead of (>=>), you get the three monad laws:

(m >>= g) >>= h = m >>= \x -> (g x >>= h)

return x >>= f = f x

m >>= return = m

So it's really not (>>=) that is the elegant operator but rather (>=>) is the symmetric operator you are looking for. However, the reason we usually think in terms of (>>=) is because that is what do notation desugars to.

Gabriella Gonzalez
  • 34,863
  • 3
  • 77
  • 135
  • Thanks for your answer. I think I kinda-sorta understand what you are saying. I'll probably have to go through this and solve a bunch of examples before I fully grasp what you are saying, but I do appreciate your reply. – ssm Apr 14 '14 at 04:19
10

Let us consider one of the common uses of the Maybe monad: handling errors. Say I wanted to divide two numbers safely. I could write this function:

safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv n d = n `div` d

Then with the standard Maybe monad, I could do something like this:

foo :: Int -> Int -> Maybe Int
foo a b = do
  c <- safeDiv 1000 b
  d <- safeDiv a c  -- These last two lines could be combined.
  return d          -- I am not doing so for clarity.

Note that at each step, safeDiv can fail, but at both steps, safeDiv takes Ints, not Maybe Ints. If >>= had this signature:

(>>=) :: Maybe a -> (a -> b) -> Maybe b

You could compose functions together, then give it either a Nothing or a Just, and either it would unwrap the Just, go through the whole pipeline, and re-wrap it in Just, or it would just pass the Nothing through essentially untouched. That might be useful, but it's not a monad. For it to be of any use, we have to be able to fail in the middle, and that's what this signature gives us:

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b

By the way, something with the signature you devised does exist:

flip fmap :: Maybe a -> (a -> b) -> Maybe b
icktoofay
  • 126,289
  • 21
  • 250
  • 231
  • 3
    +1. Indeed, `fmap :: (Functor f) => (a -> b) -> f a -> f b` *is* extremely useful, but is strictly less powerful than `(>>=)` in the sense explained above. – duplode Apr 14 '14 at 04:30
  • +1 I gather that the definition I am looking at is already a Functor? Is that correct? In that case, Ill have to look up the differences between Functors and Monads and why exactly Monads are better. I don't understand everything immediately, but now I know where to look. Thanks for the answer. – ssm Apr 14 '14 at 04:35
  • 4
    @ssm: Monads aren't *better*, you can just do more stuff with them (in some sense anyway). All Monads are Functors, so a Monad is a Functor with some extra stuff. So is Applicative for that matter. In fact, all Monads are Applicatives and all Applicatives are Functors. – David Young Apr 14 '14 at 04:41
3

The more complicated function with a -> Maybe b is the more generic and more useful one and can be used to implement the simple one. That doesn't work the other way around.

You can build a a -> Maybe b function from a function f :: a -> b:

f' :: a -> Maybe b
f' x = Just (f x)

Or, in terms of return (which is Just for Maybe):

f' = return . f

The other way around is not necessarily possible. If you have a function g :: a -> Maybe b and want to use it with the "simple" bind, you would have to convert it into a function a -> b first. But this doesn't usually work, because g might return Nothing where the a -> b function needs to return a b value.

So generally the "simple" bind can be implemented in terms of the "complicated" one, but not the other way around. Additionally, the complicated bind is often useful and not having it would make many things impossible. So by using the more generic bind monads are applicable to more situations.

sth
  • 222,467
  • 53
  • 283
  • 367
2

The problem with the alternative type signature for (>>=) is that it only accidently works for the Maybe monad, if you try it out with another monad (i.e. List monad) you'll see it breaks down at the type of b for the general case. The signature you provided doesn't describe a monadic bind and the monad laws can't don't hold with that definition.

import Prelude hiding (Monad, return)

-- assume monad was defined like this
class Monad m where
  (>>=)  :: m a -> (a -> b) -> m b
  return :: a -> m a

instance Monad Maybe where
  Nothing  >>= f = Nothing
  (Just x) >>= f = return $ f x

instance Monad [] where
  m >>= f   =  concat (map f m)
  return x  =  [x]

Fails with the type error:

    Couldn't match type `b' with `[b]'
      `b' is a rigid type variable bound by
          the type signature for >>= :: [a] -> (a -> b) -> [b]
          at monadfail.hs:12:3
    Expected type: a -> [b]
      Actual type: a -> b
    In the first argument of `map', namely `f'
    In the first argument of `concat', namely `(map f m)'
    In the expression: concat (map f m)
Stephen Diehl
  • 8,271
  • 5
  • 38
  • 56
  • 1
    How about simply `m >>= f = map f m`? – ssm Apr 14 '14 at 04:24
  • 1
    @ssm: That is a good demonstration that the type signature of `>>=` that you are proposing would be exactly equivalent in power to `Functor` (`Monad` as it exists is more powerful than `Functor`). – David Young Apr 14 '14 at 04:28
  • That's precisely the Functor definition for fmap, not a monad. – Stephen Diehl Apr 14 '14 at 04:28
  • (See also: icktoofay's answer). – duplode Apr 14 '14 at 04:31
  • @DavidYoung Oh ok Thanks! Time to revise `Functors` I think :) – ssm Apr 14 '14 at 04:32
  • 2
    @ssm, you may also find it helpful to look at the `Applicative` typeclass after `Functor` and before `Monad`. An `Applicative` functor is sort of a weaker `Monad` and might help you understand the motivation for `Monad` – unfoldr Apr 14 '14 at 04:34
  • Thanks all! This 5 minutes has been very helpful! – ssm Apr 14 '14 at 04:37
  • 2
    Also, the missing "step" that pretty much defines the difference between the function you have in mind (`fmap`) and `>>=` is a function `join :: (Monad m) => m (m a) -> m a` where `m >>= f` is equivalent to `join (fmap f m)`. So when you revisit `Monad` and you wish to understand what bind offers over `fmap`, analysing what `join` does in some examples might give you some insight. If this is too confusing you can just ignore it for now. – unfoldr Apr 14 '14 at 04:56
1

The thing that makes a monad a monad is how 'join' works. Recall that join has the type:

join :: m (m a) -> m a

What 'join' does is "interpret" a monad action that returns a monad action in terms of a monad action. So, you can think of it peeling away a layer of the monad (or better yet, pulling the stuff in the inner layer out into the outer layer). This means that the 'm''s form a "stack", in the sense of a "call stack". Each 'm' represents a context, and 'join' lets us join contexts together, in order.

So, what does this have to do with bind? Recall:

(>>=) :: m a -> (a -> m b) -> m b

And now consider that for f :: a -> m b, and ma :: m a:

fmap f ma :: m (m b)

That is, the result of applying f directly to the a in ma is an (m (m b)). We can apply join to this, to get an m b. In short,

ma >>= f = join (fmap f ma)
nomen
  • 3,626
  • 2
  • 23
  • 40