admin管理员组

文章数量:1290959

The problem

I have the following code

--filename main.hs

main :: IO ()
main = do
  loop "hello" "bye" 

loop :: String -> String -> IO ()
loop inp1 inp2 = do
  putStrLn $ "Type " ++ inp1
  userInput <- getLine
  if userInput == inp1
  then do
    putStrLn "correct answer"
    loop inp2 inp1
  else if userInput == "exit"
  then do 
    putStrLn "exiting program"
    return ()
  else do
    loop inp1 inp2

After compiling it, I can run it in the terminal and type commands such as hello bye and exit. The program works.

I made a second script to interact the with the first script

--filename test_program.hs

import System.IO (hPutStrLn, hFlush, hGetLine, hClose, hSetBuffering, BufferMode( NoBuffering,LineBuffering ))
import System.Process (createProcess,
                       waitForProcess,
                       shell,
                       proc,
                       StdStream(CreatePipe),
                       std_in,
                       std_out,
                       std_err)
import Control.Monad (when)

main :: IO ()
main = do
  (Just hin, Just hout, Just herr, ph) <- createProcess (shell "./main") 
    { std_in  = CreatePipe
    , std_out = CreatePipe
    , std_err = CreatePipe
    }

  hSetBuffering hin LineBuffering
  hSetBuffering hout NoBuffering


  putStrLn =<< hGetLine hout  
  hPutStrLn hin "bla"         
  putStrLn =<< hGetLine hout  

  hPutStrLn hin "hello"       
  putStrLn =<< hGetLine hout  

  hPutStrLn hin "exit"        
  putStrLn =<< hGetLine hout  

  _ <- waitForProcess ph
  return ()

When I run this program I expect to the following sequence of messages

  1. type hello
  2. type hello
  3. correct answer
  4. type bye
  5. exiting program

but nothing happens. If I rearrange the code in the following manner

--filename test_program.hs

import System.IO (hPutStrLn, hFlush, hGetLine, hClose, hSetBuffering, BufferMode( NoBuffering,LineBuffering ))
import System.Process (createProcess,
                       waitForProcess,
                       shell,
                       proc,
                       StdStream(CreatePipe),
                       std_in,
                       std_out,
                       std_err)
import Control.Monad (when)

main :: IO ()
main = do
  (Just hin, Just hout, Just herr, ph) <- createProcess (shell "./main") 
    { std_in  = CreatePipe
    , std_out = CreatePipe
    , std_err = CreatePipe
    }

  hSetBuffering hin LineBuffering
  hSetBuffering hout NoBuffering


  hPutStrLn hin "bla"         
  hPutStrLn hin "hello"       
  hPutStrLn hin "exit"        

  putStrLn =<< hGetLine hout  
  putStrLn =<< hGetLine hout  

  putStrLn =<< hGetLine hout  
  putStrLn =<< hGetLine hout  

  putStrLn =<< hGetLine hout  

  _ <- waitForProcess ph
  return ()

where I place all the stdin in commands in the beginning then read out the stdout, I get to see the expected sequence of messages. My question is why does it work for one case but not the other case? In my mind, if I write all the arguments first then read the outputs, or write then read should not affect the behavior of the program.

Edit 1

Thanks to suggestion by Daniel Wagner my scripts are working as expected. I have written the scripts with the fixes below

--filename main.hs
import System.IO (hSetBuffering, 
                  stdout, 
                  BufferMode(LineBuffering))

main :: IO ()
main = do
  hSetBuffering stdout LineBuffering
  loop "hello" "bye" 

loop :: String -> String -> IO ()
loop inp1 inp2 = do
  putStrLn $ "Type " ++ inp1
  userInput <- getLine
  if userInput == inp1
  then do
    putStrLn "correct answer"
    loop inp2 inp1
  else if userInput == "exit"
  then do 
    putStrLn "exiting program"
    return ()
  else do
    loop inp1 inp2
--filename test_program.hs
import System.IO  (hSetBuffering, 
                   hGetLine, 
                   hPutStrLn, 
                   BufferMode( LineBuffering ))

import System.Process (createProcess,
                       waitForProcess,
                       shell,
                       StdStream(CreatePipe),
                       std_in,
                       std_out,
                       std_err)
import Control.Monad (when)

main :: IO ()
main = do
  (Just hin, Just hout, Just herr, ph) <- createProcess (shell "./main") 
    { std_in  = CreatePipe
    , std_out = CreatePipe
    , std_err = CreatePipe
    }

  hSetBuffering hin LineBuffering



  putStrLn =<< hGetLine hout  
  hPutStrLn hin "bla"         
  putStrLn =<< hGetLine hout  
  hPutStrLn hin "hello"       
  putStrLn =<< hGetLine hout  
  hPutStrLn hin "exit"        
  putStrLn =<< hGetLine hout  
  putStrLn =<< hGetLine hout  

  _ <- waitForProcess ph
  return ()

The problem

I have the following code

--filename main.hs

main :: IO ()
main = do
  loop "hello" "bye" 

loop :: String -> String -> IO ()
loop inp1 inp2 = do
  putStrLn $ "Type " ++ inp1
  userInput <- getLine
  if userInput == inp1
  then do
    putStrLn "correct answer"
    loop inp2 inp1
  else if userInput == "exit"
  then do 
    putStrLn "exiting program"
    return ()
  else do
    loop inp1 inp2

After compiling it, I can run it in the terminal and type commands such as hello bye and exit. The program works.

I made a second script to interact the with the first script

--filename test_program.hs

import System.IO (hPutStrLn, hFlush, hGetLine, hClose, hSetBuffering, BufferMode( NoBuffering,LineBuffering ))
import System.Process (createProcess,
                       waitForProcess,
                       shell,
                       proc,
                       StdStream(CreatePipe),
                       std_in,
                       std_out,
                       std_err)
import Control.Monad (when)

main :: IO ()
main = do
  (Just hin, Just hout, Just herr, ph) <- createProcess (shell "./main") 
    { std_in  = CreatePipe
    , std_out = CreatePipe
    , std_err = CreatePipe
    }

  hSetBuffering hin LineBuffering
  hSetBuffering hout NoBuffering


  putStrLn =<< hGetLine hout  
  hPutStrLn hin "bla"         
  putStrLn =<< hGetLine hout  

  hPutStrLn hin "hello"       
  putStrLn =<< hGetLine hout  

  hPutStrLn hin "exit"        
  putStrLn =<< hGetLine hout  

  _ <- waitForProcess ph
  return ()

When I run this program I expect to the following sequence of messages

  1. type hello
  2. type hello
  3. correct answer
  4. type bye
  5. exiting program

but nothing happens. If I rearrange the code in the following manner

--filename test_program.hs

import System.IO (hPutStrLn, hFlush, hGetLine, hClose, hSetBuffering, BufferMode( NoBuffering,LineBuffering ))
import System.Process (createProcess,
                       waitForProcess,
                       shell,
                       proc,
                       StdStream(CreatePipe),
                       std_in,
                       std_out,
                       std_err)
import Control.Monad (when)

main :: IO ()
main = do
  (Just hin, Just hout, Just herr, ph) <- createProcess (shell "./main") 
    { std_in  = CreatePipe
    , std_out = CreatePipe
    , std_err = CreatePipe
    }

  hSetBuffering hin LineBuffering
  hSetBuffering hout NoBuffering


  hPutStrLn hin "bla"         
  hPutStrLn hin "hello"       
  hPutStrLn hin "exit"        

  putStrLn =<< hGetLine hout  
  putStrLn =<< hGetLine hout  

  putStrLn =<< hGetLine hout  
  putStrLn =<< hGetLine hout  

  putStrLn =<< hGetLine hout  

  _ <- waitForProcess ph
  return ()

where I place all the stdin in commands in the beginning then read out the stdout, I get to see the expected sequence of messages. My question is why does it work for one case but not the other case? In my mind, if I write all the arguments first then read the outputs, or write then read should not affect the behavior of the program.

Edit 1

Thanks to suggestion by Daniel Wagner my scripts are working as expected. I have written the scripts with the fixes below

--filename main.hs
import System.IO (hSetBuffering, 
                  stdout, 
                  BufferMode(LineBuffering))

main :: IO ()
main = do
  hSetBuffering stdout LineBuffering
  loop "hello" "bye" 

loop :: String -> String -> IO ()
loop inp1 inp2 = do
  putStrLn $ "Type " ++ inp1
  userInput <- getLine
  if userInput == inp1
  then do
    putStrLn "correct answer"
    loop inp2 inp1
  else if userInput == "exit"
  then do 
    putStrLn "exiting program"
    return ()
  else do
    loop inp1 inp2
--filename test_program.hs
import System.IO  (hSetBuffering, 
                   hGetLine, 
                   hPutStrLn, 
                   BufferMode( LineBuffering ))

import System.Process (createProcess,
                       waitForProcess,
                       shell,
                       StdStream(CreatePipe),
                       std_in,
                       std_out,
                       std_err)
import Control.Monad (when)

main :: IO ()
main = do
  (Just hin, Just hout, Just herr, ph) <- createProcess (shell "./main") 
    { std_in  = CreatePipe
    , std_out = CreatePipe
    , std_err = CreatePipe
    }

  hSetBuffering hin LineBuffering



  putStrLn =<< hGetLine hout  
  hPutStrLn hin "bla"         
  putStrLn =<< hGetLine hout  
  hPutStrLn hin "hello"       
  putStrLn =<< hGetLine hout  
  hPutStrLn hin "exit"        
  putStrLn =<< hGetLine hout  
  putStrLn =<< hGetLine hout  

  _ <- waitForProcess ph
  return ()
Share Improve this question edited Feb 17 at 17:47 greatangle asked Feb 13 at 17:37 greatanglegreatangle 112 bronze badges 1
  • My guess is that some flushing is needed in the first script too. Disabling buffering there could also work (but looks overkill). I'd attempt adding hFlush System.IO.stdout before the getLine there. – chi Commented Feb 13 at 17:45
Add a comment  | 

1 Answer 1

Reset to default 2

GHC tries to help you with buffering choices when compiling a program. Roughly: when connected to a terminal, handles are line-buffered by default, to support comfy interactive use, but they are block-buffered by default for non-terminals to support efficient program-to-program transfers.

Manually choose your buffering in main.hs to fix.

main :: IO ()
main = do
  hSetBuffering stdout LineBuffering
  loop "hello" "bye" 

You may say: "but in test_program.hs, I already set the buffering!". Unfortunately, each program has its own handles and does its own buffering. So you cannot set main.hs's buffering setup from inside test_program.hs.

本文标签: stdoutHaskell program hanging when interacting with another programStack Overflow