Not all code in do block seems to be executed [duplicate] - haskell

I'm a Haskell beginner, I'm just beginning to wrap my head around Monads, but I don't really get it yet. I'm writing a game that consists of asking the user for input, and responding. Here is a simplified version of my function:
getPoint :: IO Point
getPoint = do
putStr "Enter x: "
xStr <- getLine
putStr "Enter y: "
yStr <- getLine
return $ Point (read xStr) (read yStr)
completeUserTurn :: (Board, Player) -> IO (Board, Player)
completeUserTurn (board, player) = do
putStr $ "Enter some value: "
var1 <- getLine
putStr $ "Enter another value: "
var2 <- getLine
putStr $ "Enter a point this time: "
point <- getPoint
if (... the player entered legal values ...) then do
putStr $ "This is what would happen if you did that: {stuff} do you want to do that? (y/n) "
continue <- getLine
if continue == "y" then
return (...updated board..., ...updated player...)
else
completeUserTurn (board, player)
else do
putStr "Invalid Move!\n"
completeUserTurn (board, player)
What's happening is that the prompts will appear out of order with the text that is supposed to appear before the prompt.
Here's an example of what's happening after I compiled the code above:
1
Enter some value: Enter another value:2
3
4
Enter a point this time: Enter x: Enter y: y
Is this correct? (y/n):
The bold are the things I typed in.
Obviously, I have some major conceptual error, but I don't know what. Note that it works correctly in the interpreter and fails when compiled.

As Michael said, the issue is buffering. By default, output is buffered until you print a newline (or until the buffer is full if you have really long lines), so you'll most often see this issue when trying to do same-line prompts using putStr like you're doing.
I suggest defining a small helper function like this to take care of doing the flushing for you:
import System.IO
prompt :: String -> IO String
prompt text = do
putStr text
hFlush stdout
getLine
Now you can simply do
getPoint = do
xStr <- prompt "Enter x: "
yStr <- prompt "Enter y: "
return $ Point (read xStr) (read yStr)

The IO is happening in the correct order. The issue is buffering. If you flush stdout after each putStr, it should work as expecting. You'll need to import hFlush and stdout from System.IO.

The problem wasn't with the order of operations in the IO code. The issue was input and output is by default buffered when using stdin and stdout. This increases the performance of IO in an app, but can cause operations to appear to occur out of order when both stdin and stdout are used.
There is two solutions to this. You can use the hFlush method to force a handle (either stdin or stdout) to be flushed. Eg hFlush stdout, hFlush stdin. A simpler solution (which works fine for interactive apps) is to disable buffering altogether. You can do this by calling the methods hSetBuffering stdout NoBuffering and hSetBuffering stdin NoBuffering before you start your program (ie put those lines in your main method.

Related

How can I bind two IO () monads without executing them?

In the below code, I am using >> to concatenate IO actions together. But AFAIU, m1>>m2 gets de-sugared to m1>>=(\_.m2) and thus it is executing the first IO action right when it is binding. I want all printing to happen in the main, i.e. print statements should not get interleaved with the input statements ("Enter Code"). Since do doesn't allow me to return any other monad than IO like [IO ()]. How can I have the desired printing effect?
f :: [Int] -> IO ()
f inventory = do
putStrLn "Enter Code\n"
x <- getLine
let idx = nameToIndex x
putStrLn "Quantity\n"
y <- getLine
putStrLn "More?\n"
c <- getChar
let q = (read y :: Int)
let curM = if inventory !! idx >= q then (putStrLn "sdaf\n") else (putStrLn "Overflow!\n")
if c == 'Y' then curM>>(f (update inventory idx)) else curM
main = f [1, 2]
I'm not 100% sure I understand the problem, but I think it goes like this: you'd like to do some interactions with the user, storing up information about the interaction, then display all the information at once at the end of the entire interaction.
Here's a very simplified version of your code, that skips all the business logic and just keeps asking the user if they want to continue.
prompt = do
putStrLn "Continue?"
s <- getLine
case s of
"y" -> putStrLn "Okay, let's continue." >> prompt
_ -> return ()
main = prompt
I think the effect you're asking for is to delay the display of "Okay, let's continue." until the user has stopped hitting "y". That's no problem. There's lots of ways you can do this. The most flexible is to have prompt return the action it wants to be executed after it completes:
prompt = do
putStrLn "Continue?"
s <- getLine
case s of
"y" -> do
act <- prompt
return (putStrLn "Okay, let's continue." >> act)
_ -> return (return ())
main = do
act <- prompt
act
(There are combinators that can make this code more compact, as well.) But I don't like this design; it makes it difficult to introspect on the result of prompt. A more specialized but also more maintainable approach is to return some data describing the interaction, which the caller can then turn into an IO action summarizing things. In this case, a list of strings seems like a suitable description.
prompt = do
putStrLn "Continue?"
s <- getLine
case s of
"y" -> do
results <- prompt
return ("Okay, let's continue." : results)
_ -> return []
main = do
results <- prompt
mapM_ putStrLn results
Hopefully this explanation is clear enough that you can combine this idea with your more complicated business logic.

printing menu in terminal and choosing an option, how to? [duplicate]

This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Wrong IO actions order using putStr and getLine
I'm a haskell beginner.
I'm trying to make a program that shows a menu through terminal and ask user to introduce an option. Here is the code:
main :: IO ()
main = do
putStrLn "0 <- quit"
putStrLn "1 <- Hello"
putStr "Choose an option: "
c <- getChar
case c of
'0' -> return ()
'1' -> putChar '\n' >> putStrLn "Hello World" >> main
When I use this module in the ghci interpreter everything works like it's suposed to do.
But if i compile this with:
ghc hello.hs
and run it in the terminal, it doesn't display the line "Choose an option:" before ask for a char to be introduced.
I think this may be caused because of haskell lazy nature and I don't know how to fix it.
Any ideas?
It's not due to laziness, just buffering. Use putStrLn instead of putStr and it will work.
See Wrong IO actions order using putStr and getLine

IO happens out of order when using getLine and putStr

I'm a Haskell beginner, I'm just beginning to wrap my head around Monads, but I don't really get it yet. I'm writing a game that consists of asking the user for input, and responding. Here is a simplified version of my function:
getPoint :: IO Point
getPoint = do
putStr "Enter x: "
xStr <- getLine
putStr "Enter y: "
yStr <- getLine
return $ Point (read xStr) (read yStr)
completeUserTurn :: (Board, Player) -> IO (Board, Player)
completeUserTurn (board, player) = do
putStr $ "Enter some value: "
var1 <- getLine
putStr $ "Enter another value: "
var2 <- getLine
putStr $ "Enter a point this time: "
point <- getPoint
if (... the player entered legal values ...) then do
putStr $ "This is what would happen if you did that: {stuff} do you want to do that? (y/n) "
continue <- getLine
if continue == "y" then
return (...updated board..., ...updated player...)
else
completeUserTurn (board, player)
else do
putStr "Invalid Move!\n"
completeUserTurn (board, player)
What's happening is that the prompts will appear out of order with the text that is supposed to appear before the prompt.
Here's an example of what's happening after I compiled the code above:
1
Enter some value: Enter another value:2
3
4
Enter a point this time: Enter x: Enter y: y
Is this correct? (y/n):
The bold are the things I typed in.
Obviously, I have some major conceptual error, but I don't know what. Note that it works correctly in the interpreter and fails when compiled.
As Michael said, the issue is buffering. By default, output is buffered until you print a newline (or until the buffer is full if you have really long lines), so you'll most often see this issue when trying to do same-line prompts using putStr like you're doing.
I suggest defining a small helper function like this to take care of doing the flushing for you:
import System.IO
prompt :: String -> IO String
prompt text = do
putStr text
hFlush stdout
getLine
Now you can simply do
getPoint = do
xStr <- prompt "Enter x: "
yStr <- prompt "Enter y: "
return $ Point (read xStr) (read yStr)
The IO is happening in the correct order. The issue is buffering. If you flush stdout after each putStr, it should work as expecting. You'll need to import hFlush and stdout from System.IO.
The problem wasn't with the order of operations in the IO code. The issue was input and output is by default buffered when using stdin and stdout. This increases the performance of IO in an app, but can cause operations to appear to occur out of order when both stdin and stdout are used.
There is two solutions to this. You can use the hFlush method to force a handle (either stdin or stdout) to be flushed. Eg hFlush stdout, hFlush stdin. A simpler solution (which works fine for interactive apps) is to disable buffering altogether. You can do this by calling the methods hSetBuffering stdout NoBuffering and hSetBuffering stdin NoBuffering before you start your program (ie put those lines in your main method.

read line until ESC button in pressed in haskell

I need to real lines until ESC button is pressed. How can I check it?
lines
= do
line <- getLine
if (== "/ESC") --this condition is wrong
then ...
else do
ln <- lines
return ...
Could anybody fix my problem?
The correct way to escape is with a backslash, the character is '\ESC', so the condition would be
if line == "\ESC"
But I'm not sure every terminal passes an '\ESC' through to the application.
If you want to stop immediately when the ESC key is pressed, something along the lines of
module Main (main) where
import System.IO
main :: IO ()
main = do
hSetBuffering stdin NoBuffering
getUntilEsc ""
getUntilEsc :: String -> IO ()
getUntilEsc acc = do
c <- getChar
case c of
'\ESC' -> return ()
'\n' -> do putStrLn $ "You entered " ++ reverse acc
getUntilEsc ""
_ -> getUntilEsc (c:acc)
is what you need. You have to read character-wise, and you need to turn off the buffering of stdin, so the characters are immediately available for reading, and not only after a newline has been entered.
Note that on Windows turning off buffering didn't work. I don't know if this has been fixed recently.
Also, as #Daniel Wagner reported, it may well be that the Windows command prompt doesn't pass the ESC to the application.

Haskell: Basic Reading Int

The objective is to code the game of Nim in Haskell as a school assignment. I'm new to Haskell and get weird behavior when I try to read input.
The goal is to read two integers. Instead of printing the first message, then prompting and then going on to the second one, it just prints the two messages and I'm unable to give proper input. What is wrong here?
type Board = [Int] -- The board
type Heap = Int -- id of heap
type Turn = (Int, Int) -- heap and number of stars to remove
readInt :: String -> IO Int
readInt msg = do putStr (msg ++ "> ")
inp <- getChar
let x = ord inp
return x
readTurn :: Board -> IO(Turn)
readTurn b = do heap <- readInt "Select heap:"
amt <- readInt "Select stars:"
print heap
print amt
return(heap, amt)
The problem is that stdout is line-buffered by default, which means that nothing gets output until you print a newline. There are two ways to solve this:
Use hFlush stdout after printing the prompt to flush the buffer.
Use hSetBuffering stdout NoBuffering at the start of your program to disable output buffering.
Also, using getChar and ord will read a single character and give you its ASCII value, which is probably not what you wanted. To read and parse a number, use readLn:
import System.IO (hFlush, stdout)
readInt :: String -> IO Int
readInt msg = do
putStr (msg ++ "> ")
hFlush stdout
readLn
readChar reads only one character at a time. I assume you want instead to read a whole line, convert it to a number (possibly with more than one digit), and continue. You need to use getLine :: IO String and read :: Read a => String -> a:
readInt :: String -> IO Int
readInt msg = do
putStr (msg ++ "> ")
hFlush stdout
inp <- getLine
return (read inp)

Resources