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!

Conditionally turning on spell checking in vim

Turning on spell check in vim is easy, you just enter the following command at the prompt:

:set spell spelllang=en_au

Of course you substitute in the language code that suits you. So, when you’re tired of all of the highlighting, you can easily turn it off like so:

:set nospell

I use vim for both general text editing (say, like the Markdown document for instance) as well as for code editing. I really don’t want spell checking on for code editing, so I’ve added the following block to my .vimrc to turn it on for Markdown documents only:

autocmd FileType mkd set spell spelllang=en_au

Finally, if you’ve got a word that you’ve spelt incorrectly and you’re stuck on fixing it - put your cursor over the word and hit z=. You’ll get a list of suggestions to help you!

XMonad locks up by my window still works

I’ve just recently installed XMonad on my laptop and am just ironing out some issues that have popped up from time to time. Today, I’d noticed that XMonad had stopped responding to my mod key requests, however the focused application (in my case firefox) was still responding.

After doing some searching around the web, I’ve come across this article which has set me straight. It seems that a pipe that XMonad writes to is full - it just needs to be cleared.

First off, you’ll need to get yourelf to a terminal. If you’re unable to do that (like I was), I went to another virtual terminal all together by using alt+ctrl+f1.

You need to find XMonad’s currently running pid.

$ ps -ef | grep xmonad
michael  1406  1395  0 10:48 ?      00:00:09 /home/michael/.xmonad/xmonad-x86_64-linux

In my case, it’s 1406. You need to take a look at this pid’s file descriptors:

$ ls -l /proc/1406/fd
total 0
lr-x------ 1 michael michael 64 Jun 18 14:10 0 -> /dev/null
lrwx------ 1 michael michael 64 Jun 18 14:10 1 -> socket:[17946]
lrwx------ 1 michael michael 64 Jun 18 14:10 2 -> socket:[17946]
l-wx------ 1 michael michael 64 Jun 18 14:10 3 -> /var/log/slim.log
lrwx------ 1 michael michael 64 Jun 18 14:10 4 -> socket:[18899]
l-wx------ 1 michael michael 64 Jun 18 14:10 5 -> pipe:[18898]

So, in my case here #5 which is the pipe - needs to be cleared. You can do so really quickly just by catting it to screen.

$ cat /proc/1406/fd

XMonad should now be back in the land of the living.

Finally, this should never be a problem just as long as your xmonad.hs is configured with a logHook that will pipe the contents of this stream out.

xmproc <- spawnPipe "xmobar"

. . .
. . .

logHook = dynamicLogWithPP $ xmobarPP
         { ppOutput = hPutStrLn xmproc
         , ppTitle = xmobarColor "green" "" . shorten 50
         }

Using tailable cursors on the MongoDB oplog for realtime changes

MongoDB provides the ability to invoke to retrieve cursors of data that are tailable.

We can exploit this functionality by using on the oplog to provide a trigger-like effect on the Mongo database so that we can respond to changes in real-time.

Using pymongo you can setup a connection to your mongo server’s oplog like so:

tail_opts = { 'tailable': True, 'await_data': True }

# connect to the target mongo server
mongo_url = 'mongodb://localhost:27017'
db = MongoClient(mongo_url).local

# get the latest timestamp in the database
last_ts = db.oplog.rs.find().sort('$natural', -1)[0]['ts'];

while True:
  # prepare the tail query and kick it off
  query = { 'ts': { '$gt': last_ts } }
  cursor = db.oplog.rs.find(query, **tail_opts)
  cursor.add_option(_QUERY_OPTIONS['oplog_replay'])

  try:
     while cursor.alive:
        try:
           # grab a document if available
           doc = cursor.next()
           
           # do something interesting with "doc"

        except StopIteration:
           # thrown when the cursor is out of data, so wait
           # for a period for some more data
           time.sleep(10)
  finally:
     cursor.close()

This constant feedback loop will just keep pumping results down the pipe as they’re seen. You can already see that having an oplog setup on your database is a requirement of this solution. Without this, we have no way to measure the transactions that have executed.

The dictionary tail_opts is passed as the second argument to the find call. You can see that there are a couple of flags set here. The first one is tailable. tailable tells mongo that we want new results as they appear in scope of the cursor. await_data is another option that is set on the cursor to get the server to wait for data as it becomes available.

According to 10gen:

The sequence creates a cursor that will wait for few seconds after returning the full result set so that it can capture and return additional data added during the query

I have wrapped this functionality up into a server of its own (and client library) available from my GitHub repo. mutated-mongo takes the idea in this article and filters out only messages that particular clients have subscribed to. It’s still a work in progress.

How to setup an oplog on a single MongoDB instance

The MongoDB oplog allows you to keep track of changes that have happened on your database in real-time. This is a very useful tool that isn’t offered out of the box with a single server instance. You can follow these steps to enable to oplog on a standalone MongoDB instance.

Un-comment the following lines from your /etc/mongodb.conf file

replSet=rs0
oplogSize=1024

This will give your MongoDB server a replica set identity of rs0 and will allow your oplog to grow to 1024mb. You can tune these parameters to suit.

To complete the process, restart your MongoDB daemon and open a shell. You just need to issue rs.initiate() on the local database:

michael@mongo:~$ mongo
MongoDB shell version: 2.6.1
connecting to: test
> use local
switched to db local
> rs.initiate()
{
   "info2" : "no configuration explicitly specified -- making one",
   "me" : "mongo:27017",
   "info" : "Config now saved locally.  Should come online in about a minute.",
      "ok" : 1
   }
> show collections
me
oplog.rs
startup_log
system.indexes
system.replset

You now have the oplog available to you.