It's easy to see when re-writing the code with bind and return:
[1,2] >>= (\x-> [x,x+1]) === concatMap (\x-> [ x,x+1 ]) [1,2] [1,2] >>= (\x-> return [x,x+1]) === concatMap (\x-> [ [x,x+1] ]) [1,2]
Your first code is tantamount to calling join on the results of the second, removing one monadic "layer" introduced by return :: a -> m a, conflating the "list" of the monad in use, with the "list" of your value. If you were returning a pair, say, it wouldn't have made much sense to omit the return:
-- WRONG: type mismatch [1,2] >>= (\x-> (x,x+1)) === concatMap (\x-> ( x,x+1 )) [1,2] -- OK: [1,2] >>= (\x-> return (x,x+1)) === concatMap (\x-> [ (x,x+1) ]) [1,2]
Or, we can use a join/fmap re-write:
ma >>= famb === join (fmap famb ma) -- famb :: a -> m b, m ~ [] join (fmap (\x-> [x,x+1]) [1,2]) = concat [ [ x,x+1 ] | x<-[1,2]] join (fmap (\x-> (x,x+1)) [1,2]) = concat [ ( x,x+1 ) | x<-[1,2]] -- WRONG join (fmap (\x-> return [x,x+1]) [1,2]) = concat [ [ [x,x+1] ] | x<-[1,2]] = [y | x<-[1,2], y<-[ x,x+1 ]] {- WRONG -} = [y | x<-[1,2], y<-( x,x+1 )] = [y | x<-[1,2], y<-[[x,x+1]]]
returnin theMonadclass with thereturnkeyword in C-like imperative languages. They are completely different, but quite a lot of people used to those languages seereturnand don't realise that it's not causing a value to be returned, it's making a computation which will return that value when evaluated at some later point. Being a functional language, Haskell has no need for anything resembling areturnstatement.returnwas, imho, a huge mistake. But due to backwards compatibility, there's no going back on it now!do_not_return a = return apure.pure" is also a bad name.pure ximplies taking anxand making it "pure" (likesin xtakesxand returns the sine ofx). but in fact it's the opposite: it takes a "pure"x.