Haskell: When not to use "Do" notation

I recently suggested a change to the Gitit wiki so that it returned a 404 Not Found code rather than a 200 OK when a user tried to access a page that doesn’t exist.

Just returning a 404 code would break Gitit as there would be no way to create a new page so I wanted everything to be the same apart from the status code.

In Gitit the functions that do that page content are called Handlers and they have type

type Handler = GititServerPart Response

The function notFound that returns the 404 status code has type

notFound :: (FilterMonad Response m) => a -> m a

So in order to use the handler as an argument for notFound I had to lift (is lift the right word here?) it out of the GititServerPart monad. My solution was as follows:

handler404::Handler->Handler
handler404 handler = do
    handler'<-handler
    notFound $ handler'

I emailed this change to the Gitit-Discuss mailing list and once they realised it wouldn’t break everything John MacFarlane made some changes. When I looked at then I saw that this is what he had done (the “guardPath isPage >> createPage” part is a Handler):

notFound =<< (guardPath isPage >> createPage)

As you can see, my code is a textbook example of when using “do” notation is a bad idea.

Summary

Suppose you want to apply the function bar

bar::a->m a

to foo

type foo = m a

If this is all you want to do then do not use “do” notation; using arrows like “=<<” makes the code more concise and much easier to follow.