Learn You a Haskell for Great Good! - Miran Lipovaca [131]
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
The numbers from the list [1,2] are bound to n, and the characters from the list ['a','b'] are bound to ch. Then we do return (n, ch) (or [(n, ch)]), which means taking a pair of (n, ch) and putting it in a default minimal context. In this case, it’s making the smallest possible list that still presents (n, ch) as the result and features as little nondeterminism as possible. Its effect on the context is minimal. We’re saying, “For every element in [1,2], go over every element in ['a','b'] and produce a tuple of one element from each list.”
Generally speaking, because return takes a value and wraps it in a minimal context, it doesn’t have any extra effect (like failing in Maybe or resulting in more nondeterminism for lists), but it does present something as its result.
When you have nondeterministic values interacting, you can view their computation as a tree where every possible result in a list represents a separate branch. Here’s the previous expression rewritten in do notation:
listOfTuples :: [(Int, Char)]
listOfTuples = do
n <- [1,2]
ch <- ['a','b']
return (n, ch)
This makes it a bit more obvious that n takes on every value from [1,2] and ch takes on every value from ['a','b']. Just as with Maybe, we’re extracting the elements from the monadic values and treating them like normal values, and >>= takes care of the context for us. The context in this case is nondeterminism.
do Notation and List Comprehensions
Using lists with do notation might remind you of something you’ve seen before. For instance, check out the following piece of code:
ghci> [ (n, ch) | n <- [1,2], ch <- ['a','b'] ]
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
Yes, list comprehensions! In our do notation example, n became every result from [1,2]. For every such result, ch was assigned a result from ['a','b'], and then the final line put (n, ch) into a default context (a singleton list) to present it as the result without introducing any additional nondeterminism. In this list comprehension, the same thing happened, but we didn’t need to write return at the end to present (n, ch) as the result, because the output part of a list comprehension did that for us.
In fact, list comprehensions are just syntactic sugar for using lists as monads. In the end, list comprehensions and lists in do notation translate to using >>= to do computations that feature nondeterminism.
MonadPlus and the guard Function
List comprehensions allow us to filter our output. For instance, we can filter a list of numbers to search only for numbers whose digits contain a 7:
ghci> [ x | x <- [1..50], '7' `elem` show x ]
[7,17,27,37,47]
We apply show to x to turn our number into a string, and then we check if the character '7' is part of that string.
To see how filtering in list comprehensions translates to the list monad, we need to check out the guard function and the MonadPlus type class.
The MonadPlus type class is for monads that can also act as monoids. Here is its definition:
class Monad m => MonadPlus m where
mzero :: m a
mplus :: m a -> m a -> m a
mzero is synonymous with mempty from the Monoid type class, and mplus corresponds to mappend. Because lists are monoids as well as monads, they can be made an instance of this type class:
instance MonadPlus [] where
mzero = []
mplus = (++)
For lists, mzero represents a nondeterministic computation that has no results at all—a failed computation. mplus joins two nondeterministic values into one. The guard function is defined like this:
guard :: (MonadPlus m) => Bool -> m ()
guard True = return ()
guard False = mzero
guard takes a Boolean value. If that value is True, guard takes a () and puts it in a minimal default context that still succeeds. If the Boolean value is False, guard makes a failed monadic value. Here it is in action:
ghci> guard (5 > 2) :: Maybe ()
Just ()
ghci> guard (1 > 2) :: Maybe ()
Nothing
ghci> guard (5 > 2) :: [()]
[()]
ghci> guard (1 > 2) :: [()]
[]
This looks interesting, but how is it useful? In the list monad, we use it to filter