Cogs and Levers A blog full of technical stuff

Notes on working monadically

You can do little bits and pieces in your Haskell code to take it from an imperative looking style to being more concise and monadic in your approach. In today’s post, I’m going to run through a small sample of functions that should help this process greatly.

One final thought is that it’s not so much the use of these functions that’s important; it’s more the change in thought process to put you in a position where these functions become effective is where the true power lies.

»=

The >>= function (pronounced “bind”) sequences operations together by passing the result of what’s on the left hand side, to the right hand side. >>= looks like this:

(>>=) :: Monad m => m a -> (a -> m a) -> m b

>>= is asking for:

  • a which is a value wrapped in a monad
  • (a -> m a) which is a function accepting a value and returning a wrapped value

An example of this in action looks like this:

-- | Returns a string in context of a monad
ns :: Monad m => m [Char]
ns = return $ "Hello"

-- | Prints a string to the console
putStrLn :: String -> IO ()

-- Using bind
ns >>= putStrLn

What’s going on in the above snippet is that >>= has unwrapped the string returned by ns from its IO monad so that it’s just a string. >>= has then applied this raw value to putStrLn.

The =<< function performs the same role as >>= only it has its parameters flipped.

putStrLn =<< ns

liftM

liftM allows you to promote a function to a monad. Here’s what it looks like

liftM :: Monad m => (a1 -> r) -> m a1 -> m r

(a1 -> r) is what will get lifted into the monad, so we can keep our code that does work free of any monad awareness.

Where I have found that this is useful is when you have a set of functions that don’t operate on wrapped values, and you’d like to sequence them monadically.

My initial attempts to do this look like this:

-- | Collects all of the successfully read contents of
--   of the read files into an array of targets
allContents :: [String] -> IO [Target]
allContents paths = do
   rs <- safeReadFiles
   let cs = map parseSentinelFile (catMaybes rs)
   return $ concat $ rights cs
 where safeReadFiles = mapM safeReadFile paths

This reads like a big blob of imperative glug! So, I thought that all of these pieces could be sequenced together and chained with >>=. Here’s what I got:

allContents paths = mapM safeReadFile paths
                >>= return . catMaybes
                >>= return . mapParseSentinelFile
                >>= return . rights
                >>= return . concat

Well, at least the code is sequenced - but all of those returns sure are annoying. Here’s where liftM comes in. With liftM, we can compose all of those functions without needing to know anything about the monad that it’s executing in. Here’s what I’ve ended up with:

allContent paths = liftM p (mapP safeReadFile paths)
 where p = concat . rights . map parseSentinelFile . catMaybes

liftM has allowed us to express our function chain using . as function composition. liftM then handles all of the monadness for us!

»

The >> function performs he same sequencing as what >>= does, only the first action specified is discarded. >> looks like this:

(>>) :: Monad m => m a -> m b -> m b

This particular function comes in handy where you’re interested in not passing along a result from certain links in your sequencing chain, like this:

putStrLn "Hello. What is your name? " >>  getLine
                                      >>= putStr
                                      >>  putStrLn "! That's a great name"

From this particular sequence, you can see that

  • The action emitted from the first putStrLn is dropped
  • The action emitted from getLine is passed onto putStr
  • The action emitted from putStr is dropped
  • The last action terminates the sequence

sequence

sequence will evaluate all of the actions passed to it from left to right and return out the results of these actions. It’s defined like this

sequence :: Monad m => [m a] -> m [a]

What this allows you to do is something like this

sequence [putStr "What's your name? " >> getLine
         ,putStr "What's your age? " >> getLine
         ,putStr "What's your favourite colour? " >> getLine
         ]

This will then give you back an array of the IO actions emitted from each array index.

sequence_ will perform the same task as what sequence does, only it’ll throw away the result.

mapM

mapM will allow you to perform a monadic action over a list of normal (or unwrapped) values. It looks like this

mapM :: Monad m => (a -> m b) -> [a] -> m [b]

mapM wants:

  • A function that takes an unwrapped value a as its first parameter and returns a wrapped value m b
  • A list of unwrapped values [a]

It will then give you back a list of wrapped outputs m [b]. So, this allows you to do something like this

-- our list of unwrapped values
let questions = ["What's your name? ", "What's your age? "]

-- print out each question and ask for a response
mapM (\q -> putStr q >> getLine) questions

Also notice that in the lambda above (\q -> putStr q >> getLine), we’ve used the >> function from above as we don’t care for the action emitted from printing a string to the console.

mapM_ will perform the same task as what mapM does, only it’ll throw the result away.

filterM

filterM will filter a list based on a Bool wrapped in an action. Here’s how filterM is defined

filterM :: Monad m => (a -> m Bool) -> [a] -> m [a]

filterM will execute an action (a -> m Bool) that wants an a as its input and returns you a wrapped m Bool which will determine which a’s in the passed list [a] end up in the resulting wrapped list m [a]. Mouthful, I know. Here’s an example

filterM (\_ -> randomIO >>= return . even) [1..50]

The lambda here (\_ -> randomIO >>= return . even) actually ignores the input parameter by using \_. It’s using randomIO to grab a number out of the hat which is then bound to (return . even), which will return a wrapped Bool of if the number supplied by randomIO is even or not.

There’s heaps more that you can do. Just check out the Control.Monad namespace of the base library for some more. That’s it for today though!