planarian
planarian

Reputation: 2151

List monad: difference between `>>=` and `return` behaviors

I'm just getting started on monads, and I can't figure out why these two expressions evaluate differently:

ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]


ghci> return ([1,2],['a','b'])
([1,2],"ab")

Upvotes: 3

Views: 215

Answers (2)

Matt Fenwick
Matt Fenwick

Reputation: 49105

In GHCi, you can interactively check the type of an expression using :t. Doing so shows that your expressions have different types:

ghci> :t [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
[1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
  :: (Num t) => [(t, Char)]

ghci> :t return ([1,2],['a','b'])
return ([1,2],['a','b']) :: (Num t, Monad m) => m ([t], [Char])

Thus, they have different values.

Perhaps you're confused by the presence of monads inside the argument to return. However, look at its type:

ghci> :t return
return :: Monad m => a -> m a

return knows nothing about its argument -- it just takes a value, any value, and places it in a default, monadic context.


To understand exactly what happens when these expressions are evaluated, you'll need:

  1. Hoogle, to find the monad instance for lists, and
  2. a more specific type for the second expression

Here's the monad instance:

instance  Monad []  where
    m >>= k             = foldr ((++) . k) [] m
    m >> k              = foldr ((++) . (\ _ -> k)) [] m
    return x            = [x]
    fail _              = []

(We can ignore >> and fail, since we're not using them.)

So let's expand our expression:

[1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)

so setting m = [1, 2] and k = \n -> ['a','b'] >>= \ch -> return (n,ch) we get:

foldr ((++) . (\n -> ['a','b'] >>= \ch -> return (n,ch))) [] [1,2]

now to get rid of the second >>=, m = ['a', 'b'] and k = \ch -> return (n, ch):

foldr ((++) . (\n -> rest)) [] [1,2]
  where
    rest = foldr ((++) . (\ch -> return (n,ch))) [] ['a', 'b']

and return is easy to get rid of:

foldr ((++) . (\n -> rest)) [] [1,2]
  where
    rest = foldr ((++) . (\ch -> [(n,ch)]) [] ['a', 'b']

On the other hand, the value of the 2nd expression:

return ([1,2],['a','b'])

depends on which monad you're in. In the list monad, it simply becomes:

[([1,2], ['a','b'])] :: [] ([Int], String)

whereas in the Maybe monad, it's:

Just ([1,2], ['a', 'b']) :: Maybe ([Int], String)

Upvotes: 6

Phyx
Phyx

Reputation: 2697

The types are different, so it's reasonable that the behaviors are different

The first expression would typecheck as Num t => [(t, Char)]

The use of [] as the monad in the (>>=) means that it infers that the monad should be the List monad and in the context of the List Monad http://en.wikibooks.org/wiki/Haskell/Understanding_monads/List (>>=) is concatMap and return is (:[]).

[1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)

is the same as

concatMap (\n -> concatMap (\ch -> [(n, ch)]) ['a', 'b']) [1,2]

which returns [(1,'a'),(1,'b'),(2,'a'),(2,'b')]

In your second example what's really going on is

that the type of the expression is a bit more general:

Prelude> :t return ([1,2],['a','b'])
return ([1,2],['a','b']) :: (Monad m, Num t) => m ([t], [Char])

Because you're running it in GHCi a few things happen. GHCi can be considered a very big special IO Monad. So since no monad was specified, when GHC tries to print the results, it'll take the m Monad to be IO in this case.

The t is defaulted to Integer as well, so the type of the resulting expression is :: IO ([Integer], [Char]).

As it happens, all the types used have a Show instance, so GHC can print the results of executing the IO action, which in this case (due to the action being return) is the same as the input.

Upvotes: 10

Related Questions