Mad Libs using recursive IO for user input I'm writing my first small programs in Haskell and still getting a feel for the syntax and idioms. I've written this Mad Libs implementation using recursive IO. I've used IO actions throughout and I'm sure there must be a better way of splitting up this code to separate pure functions from IO actions. Also, I'm not happy with the printf statement, but I couldn't find a native way to apply an arbitrary number of list items to printf.

import Text.Printf

getAnswer :: String -&gt; IO String
getAnswer question = do
    putStrLn question
    answer &lt;- getLine
    return answer

getAnswers :: [String] -&gt; [String] -&gt; IO [String]
getAnswers [] ys = return ys
getAnswers (x:xs) ys = do
    answer &lt;- getAnswer x
    let answers = ys ++ [answer]
    getAnswers xs answers

main = do
    let questions = ["Enter a noun:", "Enter a verb:", "Enter an adjective:", "Enter an adverb:"]
    let madlib = "Your %s is %s up a %s mountain %s."
    answers &lt;- getAnswers questions []
    printf madlib (answers!!0) (answers!!1) (answers!!2) (answers!!3)
    putStrLn ""
Can we make getAnswer simpler or out of IO? Well, not really. You want to ask the use a question, and you want to get an answer. So all we could do is to reduce the amount of unnecessary code:

getAnswer :: String -&gt; IO String
getAnswer question = putStrLn question &gt;&gt; getLine
-- or
--                 = do
--      putStrLn question
--      getLine


However, getAnswers can be refactored quite heavily. First of all, its interface isn't really developer-friendly. What are the questions? What are the answers? We should probably hide that in the bowels of our function:

getAnswers :: [String] -&gt; IO [String]
getAnswers xs = go xs [] 
  where go [] ys = return ys
        go (x:xs) ys = do
          answer &lt;- getAnswer x
          let answers = ys ++ [answer]
          go xs answers


But ++ [...] isn't really best-practice. Instead, you would ask all other questions and then combine them:

  where go [] = return []
        go (x:xs) = do
          answer &lt;- getAnswer x
          otherAnswers &lt;- getAnswers x
          return (answer : otherAnswers)


But at that point, we're merily copying mapM's functionailty. Therefore, your getAnswers should be

getAnswers :: [String] -&gt; IO [String]
getAnswers = mapM getAnswer


A lot simpler.

Now for your main. If you don't know how many words you'll get you will need a list, correct. But lets check the structure of your result:

 "Your %s is %s up a %s mountain %s."
       1     2       3           4


There is a pattern. We have our text, then whatever the user gave us, then again our text, and so on. Let's split that into fragments:

["Your ","%s"," is ","%s"," up a ","%s"," mountain ","%s","."]
--       ^^^^        ^^^^          ^^^^              ^^^^


This brings up the following idea: if you have a list of your answers, you only need the list of the other words, right?

["Your "," is "," up a "," mountain ","."]


And then we need to "zip" that list with yours:

interleave :: [a] -&gt; [a] -&gt; [a]
interleave (x:xs) (y:ys) = x : y : interleave xs ys
interleave xs     _      = xs


We end up with the following main:

main = do
    let questions = ["Enter a noun:", "Enter a verb:", "Enter an adjective:", "Enter an adverb:"]
    let madlib = ["Your "," is "," up a "," mountain ","."]
    answers &lt;- getAnswers questions

    putStrLn $ interleave madlib questions


Here's all the code at once:

getAnswer :: String -&gt; IO String
getAnswer q = putStrLn q &gt;&gt; getLine

getAnswers :: [String] -&gt; IO [String]
getAnswers = mapM getAnswer

interleave :: [a] -&gt; [a] -&gt; [a]
interleave (x:xs) (y:ys) = x : y : interleave xs ys
interleave xs     _      = xs

main :: IO ()
main = do
    let questions = ["Enter a noun:", "Enter a verb:", "Enter an adjective:", "Enter an adverb:"]
    let madlib = ["Your "," is "," up a "," mountain ","."]
    answers &lt;- getAnswers questions

    putStrLn $ interleave madlib questions


Exercises

The interleave function above is left-biased. Why? Could this pose problems for your program? Why not?