1

Below is the code I have so far :

type Deck = [Card]
data Card = Card {question :: String, answer :: String} deriving (Show)

askForNextCommand deck = do
    putStrLn "What would you like to do?"
    userInput <- getLine
    return userInput

loop :: Deck -> IO ()
loop deck = print $ askForNextCommand deck

main :: IO ()
main = loop []

The problem I'm having is with the askForNextCommand function. I'd like to be able to use the user input in another function like this :
There's a deck of cards, each of which contain a question and answer, and the user can be quizzed on them, add more cards to the list, remove cards, etc.

I've made a similar program in Python when I was learning it so I'm trying to make it in Haskell now.

I have another function that takes the input and does something with it depending on what the input is :

doCommand command deck
    | command == "add" = addFunc deck
    | command == "remove" = removeFunc deck
    | otherwise = doCommand (askForNextCommand deck) deck

The problem is I can't figure out how to get the command parameter to be user input. I'd like askForNextCommand to prompt the user then return their input as a string, but I've been searching for about a half hour now and can't find anything. I'm sure it's an easy fix but I'm not sure where to look. Any help would be greatly appreciated.

5
  • 1
    Once a piece of data is in the IO monad, it cannot leave. What you want is fmap. Commented Oct 31, 2014 at 19:17
  • What is the type of addFunc/removeFunc? Commented Oct 31, 2014 at 19:30
  • Does it matter? Not trying to be antagonistic, just wondering why that's relevant. I guess the type signature would be addFunc :: Deck -> Deck, and in it the program would ask the user what the question is, what the answer is, etc. Commented Oct 31, 2014 at 19:32
  • @MarcusBuffett If the type includes IO, my suggested change to doCommand would be different than if it didn't. Commented Oct 31, 2014 at 19:39
  • what is the argument to askForNextCommand supposed to do? As it is written now, the argument to askForNextCommand is totally unused... Commented Oct 31, 2014 at 19:46

1 Answer 1

1

First, always provide a type signature for your toplevel bindings. This will help you greatly in figuring type errors out.

askForNextCommand :: Deck -> IO String
askForNextCommand deck = do
    putStrLn "What would you like to do?"
    userInput <- getLine
    return userInput

By the way, the last two lines are an antipattern. The standard way to write the function above would be:

askForNextCommand :: Deck -> IO String
askForNextCommand deck = do
    putStrLn "What would you like to do?"
    getLine

So far so good. Now to the culprit:

loop :: Deck -> IO ()
loop deck = print $ askForNextCommand deck

Here askForNextCommand deck is of type IO String (see the previous signature). Function print attempts to convert it to a string (technically to the type class Show), but for that it would need a function show :: IO String -> String which is impossible to construct.

Indeed, an IO String -> String would magically convert a piece of code that interacts with the user and produces a sting (e.g. getLine) into a piece of code which produces the string with no user interaction. We can not extract the string from getLine without actually doing the IO, so this is impossible.

Here's the corrected version:

loop :: Deck -> IO ()
loop deck = do
     cmd <- askForNextCommand deck
     print cmd

or, equivalently,

loop :: Deck -> IO ()
loop deck = askForNextCommand deck >>= print

The above runs the IO, takes the string, and then prints it. print receives a String now, so everything is fine.

The thumb rule is: you can extract things from the IO monad using x <- someIOValue inside a do. The catch is that you can do that only in a function which returns an IO type again.

If you want to learn more about monads and friends in Haskell, there's the excellent blog post Functors, Applicatives, And Monads In Pictures which explains them in a practical and lightweight way.

Finally, the code above will add some extra quotes. Use putStrLn instead of print is you want to avoid quotes.

Sign up to request clarification or add additional context in comments.

4 Comments

So how would I use this to pass the cmd to another function that doesn't return an IO type? Say I have the function doCommand, how do I pass the cmd to it?
@MarcusBuffett Is doCommand doing any IO? If so, it has to return an IO type. Otherwise, if it is a pure function, you can convert the pure value into an IO one with return (doCommand cmd), or instead print it print (doCommand cmd). It depends on what you want to do.
Okay so I know now how I can take a pure value and convert it to IO, how would I do the opposite? Like how would I make this work? doCommand askForNextCommand? As in, use the user input from askForNextCommand, and pass it as a string into doCommand?
I suspect the problem really is with addFunc and removeFunc. I suspect they are not pure functions, because if they do return values, they must be used somehow (and from your code it is not obvious what the return value is or how it is used), and if they don't, they must be used for side-effects and therefore should be monadic.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.