
[...] So I am still in IO Int land despite having used the >>= in the do syntax. [...]
The idea of the IO type is that it marks code that can behave differently each time it is called. For this reason, the IO monad 'infects' everything above it in the call chain and, for this reason, the entire program (i.e., Main.main) has type IO (). Rather than trying to extract an Int from the IO monad, you should look at how you can use an Int inside the IO monad. Many programs have a structure like this: read a value, compute a result, display the result. Let's suppose we have a function to do each of these three parts: readValue :: IO Int compute :: Int -> String display :: String -> IO () Note that reading and displaying have side effects and/or depend on the environment so they involve the IO monad. Since they involve a monad, let's use the do notation. Obviously, we start by reading a value: main = do x <- readValue .... The next thing to do is to compute a result. From the types, we know that x has type Int so obviously we want something involving 'compute x'. compute is not in the IO monad so we'll use a let binding instead of '<-': main = do x <- readValue let y = compute x ... Now we want to display y. We know that y has type String so we can apply display to it. 'display y' has type 'IO ()' so we use a monadic binding (<-) instead of a let binding: main = do x <- readValue let y = compute x z <- display y .... Hmmm, what to do with z. It's type is just () so we don't really need to bind it to anything so let's drop the binding to get the complete program: main = do x <- readValue let y = compute x display y Let me summarise what went on here: 1) If something has monadic type (i.e., has side effects or depends on the environment it is run in), use '<-' to bind the (non-monadic) result to a variable. 2) If something is not monadic (i.e., doesn't affect the world and doesn't depend on the world), use 'let' to bind the (non-monadic) result to a variable. 3) If something has a monadic type but we don't care about the result (so, presumably, it has an interesting side effect) you can drop the 'blah <-' part. [This last rule makes more sense if you use >>= and >> instead of the do notation. >>= corresponds to binding a result using '<-' while
corresponds to not binding a result.]
Another way of looking at it is that the IO monad infects the type of everything _above_ it in the call chain but doesn't infect the type of things _below_ it. So if you have a pure function like 'compute', you just call it from within the IO monad rather than trying to get out of the IO monad, call compute and then go back into the IO monad when you want to display the result. I hope this helps, -- Alastair Reid