Tuesday 10 November 2009

Understanding "Do" Blocks

Just some notes as I thumb through Chapter 7 of Real World Haskell.

Haskell "do" notation is a way of writing code to sequence some actions. However, it's simply just syntactic sugar for two functions, >>= and >> that have the following types:

*Main> :t (>>=)
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b

*Main> :t (>>)
(>>) :: (Monad m) => m a -> m b -> m b

>> performs the first action, discarding the result, and then the second. The result of the function is the value of the second action.

*Main System.Environment> getEnv "HOME"

*Main System.Environment> getEnv "USER"

*Main System.Environment> getEnv "HOME" >> getEnv "USER"

The result of chaining together the getEnv (String :: IO(String)) calls is to ignore the return value of the first action.

>>= takes the output of the first action, and feeds it into the second.

*Main System.Directory> canonicalizePath "foo"

*Main System.Directory> makeRelativeToCurrentDirectory "/home/haskell/foo"

*Main System.Directory> canonicalizePath "foo" >>= makeRelativeToCurrentDirectory

As well as sequencing actions using >> and >>= there are a whole family of functions to do with evaluating actions.

mapM :: (Monad m) => (a -> m b) -> [a] -> m [b] takes a list of actions and performs the action specified for each one (when evaluated, remember it's lazy!), collecting together the results.

*Main System.Environment> mapM getEnv ["HOME","USER"]

mapM_ :: (Monad m) => (a -> m b) -> [a] -> m () does exactly the same as mapM EXCEPT it discards the results. Useful for ensuring the side effects happen (for example, IO). Function names that end with a "M" are usually related to monads, and function names that end with an "_" typically discard their results.

Back to do blocks. Typically the final construct in a do block is a return statement. As I said previously return is like the opposite of <- - it takes a pure value and constructs an action out of it. Now that I understand a bit more Haskell the type definition of return makes more sense.

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

There's some more information about do notation on the Haskell wiki.