Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing loops for interactive IO: problems with do-notation and layout

Tags:

loops

io

haskell

I just started Haskell and it completely confuses me. I did Java and Python before which made a lot more sense to me.

I'm currently trying to get a user input, check if it is valid, if not: print an error and get the input again; if valid: produce a a boolean value from it.

To be more precise, I want a yes/no input, where 'y' will produce True, 'n' will produce False, and any other input will print a message into the command line and ask to input y/n again.

E.g.:

Continue? y/n:
> assd
Invalid input.
Continue? y/n:
> y
(something happens)
Continue? y/n:
> n
(Close program)

Writing it in farmiliar format, a function like this:

boolean inputBool() {
    while(True) {
       str = input("Continue? y/n: ");
       if (str == "y") {
           return True;
       } else if (str == "n") {
           return False;
       } else {
           print("Invalid input");
       }
}

--main program--

while(inputBool()) {
    doSomething();
}

Since I just started haskell (today actually), I don't have much of an idea what I can or can't do. I was thinking of something similar to:

yesno :: Bool
yesno = do
        putStr "Continue? y/n: " 
        str <- readLn
        if (str == "y") then True else (
            if (str == "n") then False else (
                do
                putStrLn "Invalid input."
                yesno
            )    
        )

Which doesn't work for many reasons. My main problem is that I don't know what this "do" does. I just read it's used when needing io operations and somehow executes the following expressions. Which doesn't make sense to me after reading that in haskell everything evaluates to a value. What does "do" evaluate to? Also, what indentation is expected? It seems to be kind of random. I know that the function has to evaluate to my boolean value, which doesn't seem to be possible with using this "do" operation. But then how do I print something to the console and still make it part of an expression that evaluates to True or False?

Thanks for any help.

(Btw. are there any active haskell forums on the internet? I couldn't find any =/)

like image 907
Maro Avatar asked Apr 17 '11 18:04

Maro


1 Answers

Since your function needs to perform IO, it must return a value in the IO monad, so its type needs to be yesno :: IO Bool, and the True and False values need to be lifted into the monad by return. You also need getLine instead of readLn, since you want a raw string, and not a parsed value (which comes with the potential for a parse error). Finally, you need to hFlush stdout before calling getLine, otherwise the prompt will still be sitting in the buffer waiting for a newline (and this requires that you import System.IO).

I've made the prompt string into an argument, so you can use the function to ask all sorts of yes/no questions:

import System.IO

yesno :: String -> IO Bool
yesno prompt = do
          putStr $ prompt ++ " y/n: "
          hFlush stdout
          str <- getLine
          case str of
            "y" -> return True
            "n" -> return False
            _   -> do
              putStrLn "Invalid input."
              yesno prompt

Then, you can call your function from an interaction loop:

main :: IO ()
main = do
         fun <- yesno "Is Haskell fun?"
         if fun
          then putStrLn "rock on"
          else putStrLn "read more tutorials"
         continue <- yesno "Continue?"
         if continue then main else return ()
like image 111
pat Avatar answered Nov 03 '22 21:11

pat