The List Monad: Ghci
The List Monad: Ghci
This context of non-determinism translates to monads very nicely. Let's go ahead and see what
the Monad instance for lists looks like:
1. instance Monad [] where
2. return x = [x]
3. xs >>= f = concat (map f xs)
4. fail _ = []
return does the same thing as pure, so we should already be familiar with return for lists. It takes a
value and puts it in a minimal default context that still yields that value. In other words, it makes a list
that has only that one value as its result. This is useful for when we want to just wrap a normal value
into a list so that it can interact with non-deterministic values.
To understand how >>= works for lists, it's best if we take a look at it in action to gain some intuition
first. >>= is about taking a value with a context (a monadic value) and feeding it to a function that takes
a normal value and returns one that has context. If that function just produced a normal value instead of
one with a context, >>= wouldn't be so useful because after one use, the context would be lost. Anyway,
let's try feeding a non-deterministic value to a function:
1. ghci> [3,4,5] >>= \x -> [x,-x]
2. [3,-3,4,-4,5,-5]
When we used >>= with Maybe, the monadic value was fed into the function while taking care of
possible failures. Here, it takes care of non-determinism for us. [3,4,5] is a non-deterministic value
and we feed it into a function that returns a non-deterministic value as well. The result is also non-
deterministic, and it features all the possible results of taking elements from the list [3,4,5] and
passing them to the function \x -> [x,-x]. This function takes a number and produces two results:
one negated and one that's unchanged. So when we use >>= to feed this list to the function, every
number is negated and also kept unchanged. The x from the lambda takes on every value from the list
that's fed to it.
To see how this is achieved, we can just follow the implementation. First, we start off with the
list [3,4,5]. Then, we map the lambda over it and the result is the following:
1. [[3,-3],[4,-4],[5,-5]]
The lambda is applied to every element and we get a list of lists. Finally, we just flatten the list and voila!
We've applied a non-deterministic function to a non-deterministic value!
Non-determinism also includes support for failure. The empty list [] is pretty much the equivalent
of Nothing, because it signifies the absence of a result. That's why failing is just defined as the empty
list. The error message gets thrown away. Let's play around with lists that fail:
1. ghci> [] >>= \x -> ["bad","mad","rad"]
2. []
3. ghci> [1,2,3] >>= \x -> []
4. []
In the first line, an empty list is fed into the lambda. Because the list has no elements, none of them can
be passed to the function and so the result is an empty list. This is similar to feeding Nothing to a
function. In the second line, each element gets passed to the function, but the element is ignored and
the function just returns an empty list. Because the function fails for every element that goes in it, the
result is a failure.
Just like with Maybe values, we can chain several lists with >>=, propagating the non-determinism:
1. ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
2. [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
The list [1,2] gets bound
to n and ['a','b'] gets 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 non-determinism
as possible. Its effect on the context
is minimal. What we're saying here
is this: 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
non-determinism for lists) but it
does present something as its result.
When you have non-deterministic values interacting, you can view their computation as a tree where
every possible result in a list represents a separate branch.
1. ghci> [1..50] >>= (\x -> guard ('7' `elem` show x) >> return x)
2. [7,17,27,37,47]
The result here is the same as the result of our previous list comprehension. How does guard achieve
this? Let's first see how guard functions in conjunction with >>:
1. ghci> guard (5 > 2) >> return "cool" :: [String]
2. ["cool"]
3. ghci> guard (1 > 2) >> return "cool" :: [String]
4. []
If guard succeeds, the result contained within it is an empty tuple. So then, we use >> to ignore that
empty tuple and present something else as the result. However, if guard fails, then so will
the return later on, because feeding an empty list to a function with >>= always results in an empty list.
A guard basically says: if this boolean is False then produce a failure right here, otherwise make a
successful value that has a dummy result of () inside it. All this does is to allow the computation to
continue.
Here's the previous example rewritten in do notation:
1. sevensOnly :: [Int]
2. sevensOnly = do
3. x <- [1..50]
4. guard ('7' `elem` show x)
5. return x
Had we forgotten to present x as the final result by using return, the resulting list would just be a list of
empty tuples. Here's this again in the form of a list comprehension:
1. ghci> [ x | x <- [1..50], '7' `elem` show x ]
2. [7,17,27,37,47]
So filtering in list comprehensions is the same as using guard.
A knight's quest
Here's a problem that really lends itself to being solved with non-determinism. Say you have a chess
board and only one knight piece on it. We want to find out if the knight can reach a certain position in
three moves. We'll just use a pair of numbers to represent the knight's position on the chess board. The
first number will determine the column he's in and the second number will determine the row.
Let's make a type synonym for the knight's current position on the chess board:
1. type KnightPos = (Int,Int)
So let's say that the knight starts at (6,2). Can he get to (6,1) in exactly three moves? Let's see. If we
start off at (6,2) what's the best move to make next? I know, how about all of them! We have non-
determinism at our disposal, so instead of picking one move, let's just pick all of them at once. Here's a
function that takes the knight's position and returns all of its next moves:
1. moveKnight :: KnightPos -> [KnightPos]
2. moveKnight (c,r) = do
3. (c',r') <- [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)
4. ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)
5. ]
6. guard (c' `elem` [1..8] && r' `elem` [1..8])
7. return (c',r')
The knight can always take one step horizontally or vertically and two steps horizontally or vertically but
its movement has to be both horizontal and vertical. (c',r') takes on every value from the list of
movements and then guard makes sure that the new move, (c',r') is still on the board. If it it's not, it
produces an empty list, which causes a failure and return (c',r') isn't carried out for that position.
This function can also be written without the use of lists as a monad, but we did it here just for kicks.
Here is the same function done with filter:
1. moveKnight :: KnightPos -> [KnightPos]
2. moveKnight (c,r) = filter onBoard
3. [(c+2,r-1),(c+2,r+1),(c-2,r-1),(c-2,r+1)
4. ,(c+1,r-2),(c+1,r+2),(c-1,r-2),(c-1,r+2)
5. ]
6. where onBoard (c,r) = c `elem` [1..8] && r `elem` [1..8]
Both of these do the same thing, so pick one that you think looks nicer. Let's give it a whirl:
1. ghci> moveKnight (6,2)
2. [(8,1),(8,3),(4,1),(4,3),(7,4),(5,4)]
3. ghci> moveKnight (8,1)
4. [(6,2),(7,3)]
Works like a charm! We take one position and we just carry out all the possible moves at once, so to
speak. So now that we have a non-deterministic next position, we just use >>= to feed it
to moveKnight. Here's a function that takes a position and returns all the positions that you can reach
from it in three moves:
1. in3 :: KnightPos -> [KnightPos]
2. in3 start = do
3. first <- moveKnight start
4. second <- moveKnight first
5. moveKnight second
If you pass it (6,2), the resulting list is quite big, because if there are several ways to reach some
position in three moves, it crops up in the list several times. The above without do notation:
1. in3 start = return start >>= moveKnight >>= moveKnight >>= moveKnight
Using >>= once gives us all possible moves from the start and then when we use >>= the second time,
for every possible first move, every possible next move is computed, and the same goes for the last
move.
Putting a value in a default context by applying return to it and then feeding it to a function with >>= is
the same as just normally applying the function to that value, but we did it here anyway for style.
Now, let's make a function that takes two positions and tells us if you can get from one to the other in
exactly three steps:
1. canReachIn3 :: KnightPos -> KnightPos -> Bool
2. canReachIn3 start end = end `elem` in3 start
We generate all the possible positions in three steps and then we see if the position we're looking for is
among them. So let's see if we can get from (6,2) to (6,1) in three moves:
1. ghci> (6,2) `canReachIn3` (6,1)
2. True
Yes! How about from (6,2) to (7,3)?
1. ghci> (6,2) `canReachIn3` (7,3)
2. False
No! As an exercise, you can change this function so that when you can reach one position from the
other, it tells you which moves to take. Later on, we'll see how to modify this function so that we also
pass it the number of moves to take instead of that number being hardcoded like it is now.