| Prev: Exercises | TOC: Contents | Next: Part II - Introduction |
Haskell's built in support for monads is split among the standard prelude, which exports the most common monad functions, and the Monad module, which contains less-commonly used monad functions. The individual monad types are each in their own libraries and are the subject of Part II of this tutorial.
The Haskell 98
standard prelude
includes the definition of the Monad class as well as a few
auxilliary functions for working with monadic data types.
Monad class
We have seen the Monad class before:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
fail :: String -> m a
-- Minimal complete definition:
-- (>>=), return
m >> k = m >>= \_ -> k
fail s = error s
|
The sequence function takes a list of monadic computations,
executes each one in turn and returns a list of the results. If any
of the computations fail, then the whole function fails:
sequence :: Monad m => [m a] -> m [a]
sequence = foldr mcons (return [])
where mcons p q = p >>= \x -> q >>= \y -> return (x:y)
|
The sequence_ function (notice the underscore) has
the same behavior as sequence but does not return
a list of results. It is useful when only the side-effects of the
monadic computations are important.
sequence_ :: Monad m => [m a] -> m () sequence_ = foldr (>>) (return ()) |
The mapM function maps a monadic computation over a list
of values and returns a list of the results. It is defined in
terms of the list map function and the sequence
function above:
mapM :: Monad m => (a -> m b) -> [a] -> m [b] mapM f as = sequence (map f as) |
There is also a version with an underscore, mapM_ which
is defined using sequence_. mapM_ operates
the same as mapM, but it doesn't return the list of values.
It is useful when only the side-effects of the monadic computation are
important.
mapM_ :: Monad m => (a -> m b) -> [a] -> m () mapM_ f as = sequence_ (map f as) |
As a simple example of the use the mapping functions, a putString
function for the IO monad could be defined as:
putString :: [Char] -> IO () putString s = mapM_ putChar s |
mapM can be used within a do block in a manner similar to
the way the map function is normally used on lists. This
is a common pattern with monads — a version of a function for use within
a monad (i.e., intended for binding) will have a signature similar to
the non-monadic version but the function outputs will be within the monad:
-- compare the non-monadic and monadic signatures map :: (a -> b) -> [a] -> [b] mapM :: Monad m => (a -> m b) -> [a] -> m [b] |
=<<)
The prelude also defines a binding function that takes it arguments
in the opposite order to the standard binding function. Since the
standard binding function is called ">>=", the reverse
binding function is called "=<<". It is useful
in circumstances where the binding operator is used as a higher-order
term and it is more convenient to have the arguments in the reversed
order. Its definition is simply:
(=<<) :: Monad m => (a -> m b) -> m a -> m b f =<< x = x >>= f |
The Monad module in the standard Haskell 98
libraries exports a number of facilities for more advanced
monadic operations. To access these facilities, simply
import Monad in your Haskell program.
Not all of the function in the Monad module
are discussed here, but you are encouraged to
explore the module
for yourself when you feel you are ready to see some of the
more esoteric monad functions.
MonadPlus class
The Monad module defines the MonadPlus
class for monads with a zero element and a plus operator:
class Monad m => MonadPlus m where
mzero :: m a
mplus :: m a -> m a -> m a
|
Several functions are provided which generalize standard list-processing
functions to monads. The mapM functions are exported in
the standard prelude and were described above.
foldM is a monadic version of foldl in which
monadic computations built from a list are bound left-to-right. The definition
is:
foldM :: (Monad m) => (a -> b -> m a) -> a -> [b] -> m a foldM f a [] = return a foldM f a (x:xs) = f a x >>= \y -> foldM f y xs |
foldM
if you consider its effect in terms of a do block:
-- this is not valid Haskell code, it is just for illustration
foldM f a1 [x1,x2,...,xn] = do a2 <- f a1 x1
a3 <- f a2 x2
...
f an xn
|
foldM.
We can use foldM to create a more poweful
query function in our sheep cloning example:
| Code available in example3.hs |
|---|
-- traceFamily is a generic function to find an ancestor traceFamily :: Sheep -> [ (Sheep -> Maybe Sheep) ] -> Maybe Sheep traceFamily s l = foldM getParent s l where getParent s f = f s -- we can define complex queries using traceFamily in an easy, clear way mothersPaternalGrandfather s = traceFamily s [mother, father, father] paternalGrandmother s = traceFamily s [father, mother] |
traceFamily function uses foldM to create a simple
way to trace back in the family tree to any depth and in any pattern.
In fact, it is probably clearer to write "traceFamily s [father, mother]"
than it is to use the paternalGrandmother function!
A more typical use of foldM is within a do block:
| Code available in example4.hs |
|---|
-- a Dict is just a finite map from strings to strings
type Dict = FiniteMap String String
-- this an auxilliary function used with foldl
addEntry :: Dict -> Entry -> Dict
addEntry d e = addToFM d (key e) (value e)
-- this is an auxiliiary function used with foldM inside the IO monad
addDataFromFile :: Dict -> Handle -> IO Dict
addDataFromFile dict hdl = do contents <- hGetContents hdl
entries <- return (map read (lines contents))
return (foldl (addEntry) dict entries)
-- this program builds a dictionary from the entries in all files named on the
-- command line and then prints it out as an association list
main :: IO ()
main = do files <- getArgs
handles <- mapM openForReading files
dict <- foldM addDataFromFile emptyFM handles
print (fmToList dict)
|
The filterM function works like the list filter
function inside of a monad. It takes a predicate function which returns a
Boolean value in the monad and a list of values. It returns, inside
the monad, a list of those values for which the predicate was True.
filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]
filterM p [] = return []
filterM p (x:xs) = do b <- p x
ys <- filterM p xs
return (if b then (x:ys) else ys)
|
Here is an example showing how filterM can be used
within the IO monad to select only the directories from a list:
| Code available in example5.hs |
|---|
import Monad
import Directory
import System
-- NOTE: doesDirectoryExist has type FilePath -> IO Bool
-- this program prints only the directories named on the command line
main :: IO ()
main = do names <- getArgs
dirs <- filterM doesDirectoryExist names
mapM_ putStrLn dirs
|
zipWithM is a monadic version of the zipWith function
on lists. zipWithM_ behaves the same but discards the output
of the function. It is useful when only the side-effects of the monadic computation
matter.
zipWithM ::(Monad m) => (a -> b -> m c) -> [a] -> [b] -> m [c] zipWithM f xs ys = sequence (zipWith f xs ys) zipWithM_ ::(Monad m) => (a -> b -> m c) -> [a] -> [b] -> m () zipWithM_ f xs ys = sequence_ (zipWith f xs ys) |
There are two functions provided for conditionally executing
monadic computations. The when function takes a
boolean argument and a monadic computation with unit "()" type
and performs the computation only when the boolean argument is
True. The unless function does
the same, except that it performs the computation unless the
boolean argument is True.
when :: (Monad m) => Bool -> m () -> m () when p s = if p then s else return () unless :: (Monad m) => Bool -> m () -> m () unless p s = when (not p) s |
ap and the lifting functionsLifting is a monadic operation that converts a non-monadic function into an equivalent function that operates on monadic values. We say that a function is "lifted into the monad" by the lifting operators. A lifted function is useful for operating on monad values outside of a do block and can also allow for cleaner code within a do block.
The simplest lifting operator is liftM, which lifts a
function of a single argument into a monad.
liftM :: (Monad m) => (a -> b) -> (m a -> m b)
liftM f = \a -> do { a' <- a; return (f a') }
|
Lifting operators are also provided for functions with more arguments.
liftM2 lifts functions of two arguments:
liftM2 :: (Monad m) => (a -> b -> c) -> (m a -> m b -> m c)
liftM2 f = \a b -> do { a' <- a; b' <- b; return (f a' b') }
|
liftM5 are defined
in the Monad module.
To see how the lifting operators allow more concise code, consider a
computation in the Maybe monad in which you want to use
a function swapNames::String -> String. You could
do:
getName :: String -> Maybe String
getName name = do let db = [("John", "Smith, John"), ("Mike", "Caine, Michael")]
tempName <- lookup name db
return (swapNames tempName)
|
liftM function, we can use
liftM swapNames as a function of type
Maybe String -> Maybe String:
| Code available in example6.hs |
|---|
getName :: String -> Maybe String
getName name = do let db = [("John", "Smith, John"), ("Mike", "Caine, Michael")]
liftM swapNames (lookup name db)
|
The lifting functions also enable very concise constructions using
higher-order functions. To understand this example code,
you might need to review the definition of the monad functions for the
List monad (particularly >>=).
Imagine how you might implement this function without lifting the operator:
| Code available in example7.hs |
|---|
-- allCombinations returns a list containing the result of -- folding the binary operator through all combinations -- of elements of the given lists -- For example, allCombinations (+) [[0,1],[1,2,3]] would be -- [0+1,0+2,0+3,1+1,1+2,1+3], or [1,2,3,2,3,4] -- and allCombinations (*) [[0,1],[1,2],[3,5]] would be -- [0*1*3,0*1*5,0*2*3,0*2*5,1*1*3,1*1*5,1*2*3,1*2*5], or [0,0,0,0,3,5,6,10] allCombinations :: (a -> a -> a) -> [[a]] -> [a] allCombinations fn [] = [] allCombinations fn (l:ls) = foldl (liftM2 fn) l ls |
There is a related function called ap that is sometimes
more convenient to use than the lifting functions. ap
is simply the function application operator ($) lifted
into the monad:
ap :: (Monad m) => m (a -> b) -> m a -> m b ap = liftM2 ($) |
liftM2 f x y is equivalent to
return f `ap` x `ap` y, and so on for
functions of more arguments. ap is useful when
working with higher-order functions and monads.
The effect of ap depends on the strategy of the monad in
which it is used. So for example [(*2),(+3)] `ap` [0,1,2]
is equal to [0,2,4,3,4,5] and
(Just (*2)) `ap` (Just 3) is Just 6.
Here is a simple example that shows how ap can be useful
when doing higher-order computations:
| Code available in example8.hs |
|---|
-- lookup the commands and fold ap into the command list to
-- compute a result.
main :: IO ()
main = do let fns = [("double",(2*)), ("halve",(`div`2)),
("square",(\x->x*x)), ("negate", negate),
("incr",(+1)), ("decr",(+(-1)))
]
args <- getArgs
let val = read (args!!0)
cmds = map ((flip lookup) fns) (words (args!!1))
print $ foldl (flip ap) (Just val) cmds
|
MonadPlus
There are two functions in the Monad module that are
used with monads that have a zero and a plus. The first function
is msum, which is analogous to the sum
function on lists of integers. msum operates on lists
of monadic values and folds the mplus operator into
the list using the mzero element as the initial value:
msum :: MonadPlus m => [m a] -> m a msum xs = foldr mplus mzero xs |
In the List monad, msum is equivalent to concat.
In the Maybe monad, msum returns the first
non-Nothing value from a list. Likewise, the behavior in
other monads will depend on the exact nature of their mzero
and mplus definitions.
msum allows many recursive functions and folds to be
expressed more concisely. In the Maybe monad, for example,
we can write:
| Code available in example9.hs |
|---|
type Variable = String type Value = String type EnvironmentStack = [[(Variable,Value)]] -- lookupVar retrieves a variable's value from the environment stack -- It uses msum in the Maybe monad to return the first non-Nothing value. lookupVar :: Variable -> EnvironmentStack -> Maybe Value lookupVar var stack = msum $ map (lookup var) stack |
lookupVar :: Variable -> EnvironmentStack -> Maybe Value
lookupVar var [] = Nothing
lookupVar var (e:es) = let val = lookup var e
in maybe (lookupVar var es) Just val
|
The second function for use with monads with a zero and a plus is
the guard function:
guard :: MonadPlus m => Bool -> m () guard p = if p then return () else mzero |
mzero >>= f == mzero.
So, placing a guard function in a sequence of monadic
operations will force any execution in which the guard is False
to be mzero. This is similar to the way that guard predicates
in a list comprehension cause values that fail the predicate to become
[].
Here is an example demonstrating the use of the guard function
in the Maybe monad.
| Code available in example10.hs |
|---|
data Record = Rec {name::String, age::Int} deriving Show
type DB = [Record]
-- getYoungerThan returns all records for people younger than a specified age.
-- It uses the guard function to eliminate records for ages at or over the limit.
-- This is just for demonstration purposes. In real life, it would be
-- clearer to simply use filter. When the filter criteria are more complex,
-- guard becomes more useful.
getYoungerThan :: Int -> DB -> [Record]
getYoungerThan limit db = mapMaybe (\r -> do { guard (age r < limit); return r }) db
|
Haskell provides a number of functions which are useful for working with monads
in the standard libraries. The Monad class and most common
monad functions are in the standard prelude. The MonadPlus class
and less commonly-used (but still very useful!) functions are defined
in the Monad module. Many other types in the Haskell libraries
are declared as instances of Monad and MonadPlus
in their respective modules.
| Prev: Exercises | TOC: Contents | Next: Part II - Introduction |