
On 2/4/07, Martin Huschenbett
Hi,
I've often got the same pattern with nested Maybes but inside the IO monad (sure this could be every other monad too). Assuming that I've got functions:
This is where my favorite part of the mtl steps in: monad transformers. First, we'll create a transformed version of the IO monad, which encompasses the idea of failure. I've made the failures somewhat more general by allowing String typed error messages, but you can replace String with whatever type you'd like (including () if you really don't want any such information).
newtype MyIO a = MyIO { runMyIO :: ErrorT String IO a } deriving (Functor, Monad, MonadError String)
This uses GHC's newtype deriving mechanism, and thus requires -fglasgow-exts. The same effect can be achieved in Haskell 98 by using a type synonym instead of a newtype. Then, we need to have your operations produce their results in MyIO a instead of IO (Maybe a):
getInput :: MyIO Input processInput :: Input -> MyIO Result printError :: String -> MyIO () printResult :: Result -> MyIO ()
Finally, we can rewrite your main function without the case statements:
main = runErrorT . runMyIO $ (do input <- getInput result <- processInput input printResult result) `catchError` printError
However, in this case you don't really need do notation at all. You have a very nice pipeline of operations, and we can express it that way:
main' = runErrorT . runMyIO $ (getInput >>= processInput >>= printResult) `catchError` printError
which should remove the last vestiges of imperative-feeling code. /g -- It is myself I have never met, whose face is pasted on the underside of my mind.