10

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.

4
  • 6
    Because ma >>= k = join $ fmap k ma is more powerful. Your variant is just alias to fmap, ma >>= k = fmap k ma ~ (>>=) = flip fmap. Commented Nov 15, 2017 at 21:09
  • 1
    It's not burden, it's an opportunity. The whole point of monads is that they are not just functors with fmap and <$>. Of course in your specific example, those would have sufficed. Commented Nov 15, 2017 at 21:40
  • >>= wasn't defined out of thin error; a function of type a -> m b for a monad m is a Kleisli arrow, and its use is firmly grounded in category theory. Monads are useful in programming because they "trap" values, making it easy to lift a value into the monad but hard to get out. Commented Nov 15, 2017 at 22:38
  • [0,1..] >>= \i -> [i * 2] doesn't return [[0], [2]..] it returns [0,2,4,6,8,..] just like fmap (*2) would do however with the bind operator you have the option to use return to make it return a monad properly so [0,1..] >>= \i -> return [i * 2] would result [[0],[2],[4],[6],[8],..]. Yet with fmap its not the case take 5 $ (\i -> [i * 2]) <$> [0..] would return [[0],[2],[4],[6],[8],..] Commented Nov 15, 2017 at 22:59

2 Answers 2

14

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.

Sign up to request clarification or add additional context in comments.

4 Comments

You can get these results with just fmap... but it wouldn't be sensible to (e.g. fmap (\x -> 2*x+1) [0..] is [1,3..], and fmap (\x -> x + mod x 2 * (div x 2 - 1)) [0..] is your [0,0,2,3,4,6,6,9...] value). But make the input list finite and all of a sudden the shape changes, which is not an effect fmap can have.
The point was that your can't produce fewer or more than one value with fmap.
I agree that's the point. And I think it's easy to make that point crystal clear: just avoid infinite lists in your examples.
Well, those were the OP's examples. I was trying to keep close to the original to minimize cognitive overhead.
1

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.