Reputation: 11366
I'm new to Haskell and have been trying to better understand the IO monad (after playing with pure functions for a while).
I'm following a tutorial on the IO monad
One of the exercises is making a while function. They don't show an example so you can't check your answer.
Here is mine:
while :: IO Bool -> IO ()
while action = do p <- action
if p then putStrLn "You win!" >> return ()
else putStrLn "Nope. Try again!" >> while action
main = do putStrLn "Come and guess the letter!"
while (constAskForC)
where constAskForC = do c <- getChar
return $ c == 'c'
Now, my issue is that if you enter a wrong character (pretty much a character that is not 'c'), then string "Nope. Try again!" gets printed twice to StdOut. Why is this? Here's the program running:
Come and guess the letter!
"Nope. Try again!"
"Nope. Try again!"
d
"Nope. Try again!"
"Nope. Try again!"
"Nope. Try again!"
"Nope. Try again!"
a
"Nope. Try again!"
"Nope. Try again!"
d
"Nope. Try again!"
"Nope. Try again!"
f
"Nope. Try again!"
"Nope. Try again!"
a
"Nope. Try again!"
"Nope. Try again!"
s
"Nope. Try again!"
"Nope. Try again!"
If you just hit enter (enter no character) then it only gets printed once. Can anyone explain to me what am I doing wrong?
Thanks.
Upvotes: 1
Views: 727
Reputation: 19637
The problem here is an interaction of the behaviour of the getChar
command with the normal
buffering behaviour of a compiled program, which is to use line buffering.
The getChar
command consumes only a single character. In particular, hitting return creates a newline character on its own.
However, with line buffering, no input will actually arrive before you enter a complete line. So if you interleave a character and the Return key, two characters will be generated in one go, leading to the strange output.
You can fix this by adding the line
import System.IO
in the beginning and then adding the statement
hSetBuffering stdin NoBuffering
to the main
program. Alternatively, use GHCi which uses NoBuffering
by default.
Upvotes: 4
Reputation: 11963
The problem is in the constAskForC
function. You use getChar
, but that will only read one character. So you read c, and after you read c
, you will get the end-of-line character (\n
). There is really no way to get single characters, but you can get a whole line and only take the first character:
main = do putStrLn "Come and guess the letter!"
while (constAskForC)
where constAskForC = do c <- getLine
return $ case c of
[] -> False -- There was no input
'c':[] -> True -- The first letter was a c, and nothing else was entered
_ -> False -- Otherwise, the result is False
Another minor note on your code: putStrLn "You win!" >> return ()
is the same as putStrLn "You win!"
Upvotes: 4
Reputation: 24802
When you type a character and press enter that's actually two characters (the printable character plus a linefeed character). You probably want to use getLine
instead.
Upvotes: 2