List comprehension is very easy to understand. Look at h in the following definition. It uses pure_xs of type [Int], and pure_f of type Int -> String, using both in the list comprehension.
pure_xs :: [Int]
pure_xs = [1,2,3]
pure_f :: Int -> String
pure_f a = show a
h :: [(Int,Char)]
h = [(a,b) | a <- pure_xs, b <- pure_f a]
-- h => [(4,'4'),(5,'5'),(6,'6')]
Great. Now take two slightly different expressions, monadic_f and monadic_xs. I would like to construct g using list comprehensions, to look as similar to h as possible. I have a feeling that a solution will involve generating a sequence of IO actions, and using sequence to generate a list of type [(Int,Char)] in the IO monad.
monadic_xs :: IO [Int]
monadic_xs = return [1,2,3]
monadic_f :: Int -> IO String
monadic_f a = return (show a)
g :: IO [(Int,Char)]
g = undefined -- how to make `g` function look
-- as similar to `h` function as possible, i.e. using list comprehension?
-- g => IO [(4,'4'),(5,'5'),(6,'6')]