Hi,
You can consider that:
type IO a = World -> (World, a)
Where World is the state of the impure world.
So when you have:
getLine :: IO String
putStrLn :: String -> IO ()
Is is in fact:
getLine :: World -> (World, String)
putStrLn :: String -> World -> (World, ())
You can compose IO actions with:
(>>=) :: IO a -> (a -> IO b) -> IO b
(>>=) :: (World -> (World,a)) -> (a -> World -> (World,b)) -> World -> (World,b)
(>>=) f g w = let (w2,a) = f w in g a w2
do-notation is just syntactic sugar for this operator.
So there is an implicit dependency between both IO functions: the state of the World (which obviously doesn't appear in the compiled code).