Yet Another Haskell Tutorial/Monads/Solutions
Do Notation
[edit | edit source]Translation Rule 1
[edit | edit source]Translation Rule 2
[edit | edit source]Translation Rule 3
[edit | edit source]Translation Rule 4
[edit | edit source]Definition
[edit | edit source]Law 1
[edit | edit source]Law 2
[edit | edit source]Law 3
[edit | edit source]A Simple State Monad
[edit | edit source]Common Monads
[edit | edit source]The first law is: return a >>= f
≡ f a
. In the case
of Maybe
, we get:
return a >>= f ==> Just a >>= \x -> f x ==> (\x -> f x) a ==> f a
The second law is: f >>= return
≡ f
. Here, we get:
f >>= return ==> f >>= \x -> return x ==> f >>= \x -> Just x
At this point, there are two cases depending on whether f
is
Nothing
or not. In the first case, we get:
==> Nothing >>= \x -> Just x ==> Nothing ==> f
In the second case, f
is Just a
. Then, we get:
==> Just a >>= \x -> Just x ==> (\x -> Just x) a ==> Just a ==> f
And the second law is shown. The third law states: f >>= (\x
-> g x >>= h)
≡ (f >>= g) >>= h
.
If f
is Nothing
, then the left-hand-side clearly reduces to
Nothing
. The right-hand-side reduces to Nothing >>= h
which
in turn reduces to Nothing
, so they are the same.
Suppose f
is Just a
. Then the LHS reduces to g a >>= h
and the RHS reduces to (Just a >>= \x -> g x) >>= h
which in turn
reduces to g a >>= h
, so these two are the same.
The idea is that we wish to use the Left
constructor to represent
errors on the Right
constructor to represent successes. This
leads to an instance declaration like:
instance Monad (Either String) where return x = Right x Left s >>= _ = Left s Right x >>= f = f x fail s = Left s
If we try to use this monad to do search, we get:
Example:
Monads> searchAll gr 0 3 :: Either String [Int] Right [0,1,3] Monads> searchAll gr 3 0 :: Either String [Int] Left "no path"
which is exactly what we want.
Monadic Combinators
[edit | edit source]MonadPlus
[edit | edit source]The order to mplus
essentially determins the search order. When
the recursive call to searchAll2
comes first, we are doing
depth-first search. When the recursive call to search'
comes
first, we are doing breadth-first search. Thus, using the list monad,
we expect the solutions to come in the other order:
Example:
MPlus> searchAll3 gr 0 3 :: [[Int]] [[0,2,3],[0,1,3]]
Just as we expected.
Monad Transformers
[edit | edit source]This is a very difficult problem; if you found that you were stuck immediately, please just read as much of this solution as you need to try it yourself.
First, we need to define a list transformer monad. This looks like:
newtype ListT m e = ListT { unListT :: m [e] }
The ListT
constructor simply wraps a monadic action (in monad
m
) which returns a list.
We now need to make this a monad:
instance Monad m => Monad (ListT m) where return x = ListT (return [x]) fail s = ListT (return [] ) ListT m >>= k = ListT $ do l <- m l' <- mapM (unListT . k) l return (concat l')
Here, success is designated by a monadic action which returns a
singleton list. Failure (like in the standard list monad) is
represented by an empty list: of course, it's actually an empty list
returned from the enclosed monad. Binding happens essentially by
running the action which will result in a list l
. This has type
[e]
. We now need to apply k
to each of these elements
(which will result in something of type ListT m [e2]
. We need to
get rid of the ListT
s around this (by using unListT
) and
then concatenate them to make a single list.
Now, we need to make it an instance of MonadPlus
instance Monad m => MonadPlus (ListT m) where mzero = ListT (return []) ListT m1 `mplus` ListT m2 = ListT $ do l1 <- m1 l2 <- m2 return (l1 ++ l2)
Here, the zero element is a monadic action which returns an empty list. Addition is done by executing both actions and then concatenating the results.
Finally, we need to make it an instance of MonadTrans
:
instance MonadTrans ListT where lift x = ListT (do a <- x; return [a])
Lifting an action into ListT
simply involves running it and
getting the value (in this case, a
) out and then returning the
singleton list.
Once we have all this together, writing searchAll6
is fairly
straightforward:
searchAll6 g@(Graph vl el) src dst | src == dst = do lift $ putStrLn $ "Exploring " ++ show src ++ " -> " ++ show dst return [src] | otherwise = do lift $ putStrLn $ "Exploring " ++ show src ++ " -> " ++ show dst search' el where search' [] = mzero search' ((u,v,_):es) | src == u = (do path <- searchAll6 g v dst return (u:path)) `mplus` search' es | otherwise = search' es
The only change (besides changing the recursive call to call
searchAll6
instead of searchAll2
) here is that we call
putStrLn
with appropriate arguments, lifted into the monad.
If we look at the type of searchAll6
, we see that the result
(i.e., after applying a graph and two ints) has type MonadTrans t,
MonadPlus (t IO) => t IO [Int])
. In theory, we could use this with
any appropriate monad transformer; in our case, we want to use
ListT
. Thus, we can run this by:
Example:
MTrans> unListT (searchAll6 gr 0 3) Exploring 0 -> 3 Exploring 1 -> 3 Exploring 3 -> 3 Exploring 2 -> 3 Exploring 3 -> 3 MTrans> it [[0,1,3],[0,2,3]]
This is precisely what we were looking for.
This exercise is actually simpler than the previous one. All we need
to do is incorporate the calls to putT
and getT
into
searchAll6
and add an extra lift to the IO calls. This extra
lift is required because now we're stacking two transformers on top of
IO instead of just one.
searchAll7 g@(Graph vl el) src dst | src == dst = do lift $ lift $ putStrLn $ "Exploring " ++ show src ++ " -> " ++ show dst visited <- getT putT (src:visited) return [src] | otherwise = do lift $ lift $ putStrLn $ "Exploring " ++ show src ++ " -> " ++ show dst visited <- getT putT (src:visited) if src `elem` visited then mzero else search' el where search' [] = mzero search' ((u,v,_):es) | src == u = (do path <- searchAll7 g v dst return (u:path)) `mplus` search' es | otherwise = search' es
The type of this has grown significantly. After applying the graph
and two ints, this has type Monad (t IO), MonadTrans t, MonadPlus
(StateT [Int] (t IO)) => StateT [Int] (t IO) [Int]
.
Essentially this means that we've got something that's a state
transformer wrapped on top of some other arbitrary transformer
(t
) which itself sits on top of IO
. In our case, t
is
going to be ListT
. Thus, we run this beast by saying:
Example:
MTrans> unListT (evalStateT (searchAll7 gr4 0 3) []) Exploring 0 -> 3 Exploring 1 -> 3 Exploring 3 -> 3 Exploring 0 -> 3 Exploring 2 -> 3 Exploring 3 -> 3 MTrans> it [[0,1,3],[0,2,3]]
And it works, even on gr4
.
Parsing Monads
[edit | edit source]A Simple Parsing Monad
[edit | edit source]First we write a function spaces
which will parse out
whitespaces:
spaces :: Parser () spaces = many (matchChar isSpace) >> return ()
Now, using this, we simply sprinkle calls to spaces
through
intList
to get intListSpace
:
intListSpace :: Parser [Int] intListSpace = do char '[' spaces intList' `mplus` (char ']' >> return []) where intList' = do i <- int spaces r <- (char ',' >> spaces >> intList') `mplus` (char ']' >> return []) return (i:r)
We can test that this works:
Example:
Parsing> runParser intListSpace "[1 ,2 , 4 \n\n ,5\n]" Right ("",[1,2,4,5]) Parsing> runParser intListSpace "[1 ,2 , 4 \n\n ,a\n]" Left "expecting char, got 'a'"
=== Parsec ===
We do this by replacing the state functions with push and pop functions as follows:
parseValueLet2 :: CharParser (FiniteMap Char [Int]) Int parseValueLet2 = choice [ int , do string "let " c <- letter char '=' e <- parseValueLet2 string " in " pushBinding c e v <- parseValueLet2 popBinding c return v , do c <- letter fm <- getState case lookupFM fm c of Nothing -> unexpected ("variable " ++ show c ++ " unbound") Just (i:_) -> return i , between (char '(') (char ')') $ do e1 <- parseValueLet2 op <- oneOf "+*" e2 <- parseValueLet2 case op of '+' -> return (e1 + e2) '*' -> return (e1 * e2) ] where pushBinding c v = do fm <- getState case lookupFM fm c of Nothing -> setState (addToFM fm c [v]) Just l -> setState (addToFM fm c (v:l)) popBinding c = do fm <- getState case lookupFM fm c of Just [_] -> setState (delFromFM fm c) Just (_:l) -> setState (addToFM fm c l)
The primary difference here is that instead of calling
updateState
, we use two local functions, pushBinding
and
popBinding
. The pushBinding
function takes a variable name
and a value and adds the value onto the head of the list pointed to in
the state FiniteMap
. The popBinding
function looks at the
value and if there is only one element on the stack, it completely
removes the stack from the FiniteMap
; otherwise it just removes
the first element. This means that if something is in the
FiniteMap
, the stack is never empty.
This enables us to modify only slightly the usage case; this time, we simply take the top element off the stack when we need to inspect the value of a variable.
We can test that this works:
Example:
ParsecI> runParser parseValueLet2 emptyFM "stdin" "((let x=2 in 3+4)*x)" Left "stdin" (line 1, column 20): unexpected variable 'x' unbound