patternMinor
Mad Libs using recursive IO for user input
Viewed 0 times
usermadinputrecursiveusingforlibs
Problem
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 -> IO String
getAnswer question = do
putStrLn question
answer [String] -> IO [String]
getAnswers [] ys = return ys
getAnswers (x:xs) ys = do
answer <- 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 <- getAnswers questions []
printf madlib (answers!!0) (answers!!1) (answers!!2) (answers!!3)
putStrLn ""Solution
Can we make
However,
But
But at that point, we're merily copying
A lot simpler.
Now for your
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:
This brings up the following idea: if you have a list of your answers, you only need the list of the other words, right?
And then we need to "zip" that list with yours:
We end up with the following
Here's all the code at once:
Exercises
The
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 -> IO String
getAnswer question = putStrLn question >> getLine
-- or
-- = do
-- putStrLn question
-- getLineHowever,
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] -> IO [String]
getAnswers xs = go xs []
where go [] ys = return ys
go (x:xs) ys = do
answer <- getAnswer x
let answers = ys ++ [answer]
go xs answersBut
++ [...] isn't really best-practice. Instead, you would ask all other questions and then combine them:where go [] = return []
go (x:xs) = do
answer <- getAnswer x
otherAnswers <- getAnswers x
return (answer : otherAnswers)But at that point, we're merily copying
mapM's functionailty. Therefore, your getAnswers should begetAnswers :: [String] -> IO [String]
getAnswers = mapM getAnswerA 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 4There 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] -> [a] -> [a]
interleave (x:xs) (y:ys) = x : y : interleave xs ys
interleave xs _ = xsWe 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 <- getAnswers questions
putStrLn $ interleave madlib questionsHere's all the code at once:
getAnswer :: String -> IO String
getAnswer q = putStrLn q >> getLine
getAnswers :: [String] -> IO [String]
getAnswers = mapM getAnswer
interleave :: [a] -> [a] -> [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 <- getAnswers questions
putStrLn $ interleave madlib questionsExercises
The
interleave function above is left-biased. Why? Could this pose problems for your program? Why not?Code Snippets
getAnswer :: String -> IO String
getAnswer question = putStrLn question >> getLine
-- or
-- = do
-- putStrLn question
-- getLinegetAnswers :: [String] -> IO [String]
getAnswers xs = go xs []
where go [] ys = return ys
go (x:xs) ys = do
answer <- getAnswer x
let answers = ys ++ [answer]
go xs answerswhere go [] = return []
go (x:xs) = do
answer <- getAnswer x
otherAnswers <- getAnswers x
return (answer : otherAnswers)getAnswers :: [String] -> IO [String]
getAnswers = mapM getAnswer"Your %s is %s up a %s mountain %s."
1 2 3 4Context
StackExchange Code Review Q#146629, answer score: 3
Revisions (0)
No revisions yet.