Haskell "do" notation is a way of writing code to sequence some actions. However, it's simply just syntactic sugar for two functions,
>>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
>>=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
returnis 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.