Haskell >>= operation: why is the function argument required to return another Monad?

Viewed 222

Considering the expression:

[0,1..] >>= \i -> [i * 2]

In the definition of >>= for List, the lambda function \i -> [i * 2] is mapped over the list argument via fmap, resulting in a list of lists, [[0], [2]..]. So >>= needs to flatten the result using a join function in order to return the list: [0, 2..]

According to this source: "...the definition of bind in terms of fmap and join works for every monad m: ma >>= k = join $ fmap k ma"

So why is it necessary to place the burden of returning a monad on the function supplied to >>=? Why not simply define bind like so?

ma >>= k = fmap k ma

This way you don't have to deal with flattening the result.

2 Answers

What you're proposing is to simply define the bind operator to be equal to fmap, but with arguments swapped:

ma >>= k = fmap k ma
-- is equivalent to:
(>>=) = flip fmap

In which case, why not just use the fmap itself, or its operator form <$>?

(*2) <$> [0,1..]
> [0,2,4,6,...]

However, this does not cover all cases where bind may be used. The difference is that monads are more powerful than functors. Where functors only let you produce exactly one output for every input, monads let you do all kinds of crazy stuff.

Consider, for example, the following:

[0,1,2,3] >>= \i -> [i*2, i*3]
> [0,0,2,3,4,6,6,9]

Here, the function produces two values for each input. This cannot be expressed via just fmap. This requires joining the resulting values.

Here's another, even less obvious example:

[0,1,2,3,4,5] >>= \i -> if i `mod` 2 == 0 then [] else [i]
> [1,3,5]

Here, the function either produces a value or doesn't. The empty list is technically still a value in the List monad, yet it cannot be obtained by fmaping the input.

A major feature of a monad is to be able to "flatten" (join). This is necessary to define a nice form of composition.

Consider the composition of two functions with side effects, say in the IO monad:

foo :: A -> IO B
bar :: B -> IO C

If >>= was only fmap (without the final join) pass, the composition

\a -> foo a >>= bar

would mean

\a -> fmap bar (foo a)
-- i.e.
fmap bar . foo

which does look as a nice composition, but unfortunately has type A -> IO (IO C) ! If we can't flatten the nested IO, every composition would add yet another layer, and we would end up with result types of the form IO (IO (IO ...)) showing the number of compositions.

At best, this would be inconvenient. At worst, this would prevent recursion such as

loop = readLn >>= \x -> print x >>= \_ -> loop

since it leads to a type with an infinite nesting of IO.

Related