Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell - How do I break out of interact?

I am using interact to process some user inputs step-by-step (specifically, it's a chess program). However, I haven't found a way to deal with the situation where the user might want to just break out of the loop and start this match of chess from the beginning.

When I'm executing a normal procedure in ghci, pressing Ctrl-C will not exit the whole ghci, but will just stop the procedure itself and allow me to go on with some other procedures. However, if I press Ctrl-C in the console with the interact function in effect, the following message is shown:

^CInterrupted.
*Main> 
<stdin>: hGetChar: illegal operation (handle is closed)

And then I have to launch ghci all over again.

I also thought of catching special user inputs such as "exit", however, since the type of interact is interact :: (String -> String) -> IO (), the input will have to go through the function typed (String -> String) first, and I haven't found a way for that function to notify the main IO that it should quit.

How should I break out of interact? Or is interact not intended to be used this way and I should compose custom IO functions?

like image 606
xji Avatar asked May 13 '16 09:05

xji


1 Answers

How should I break out of interact?

You can't. You can think of interact f as getContents >>= putStrLn . f. And getContents will close the handle on stdin. Any further operation concerning reading will fail.

The literal character ^D gets shown in the terminal

That's a problem with readline. GHCi changes the buffering method of stdin from LineBuffer to NoBuffering to use readline optimally. If you want to exit interact with ^D, you need to change the buffering method:

ghci> import System.IO
ghci> hGetBuffering stdin
NoBuffering
ghci> hSetBuffering stdin LineBuffering
ghci> interact id
hello world
hello world
pressing control-D after the next RETURN
pressing control-D after the next RETURN
<stdin>: hGetBuffering: illegal operation (handle is closed)

Or is interact not intended to be used this way and I should compose custom IO functions?

Yes, it's not intended to be used this way. interact is meant to use all input and dictate all output. If you want to use use line-wise input, you can write your own line-wise interact method (or use an external library):

import Control.Monad (when)

interactLine :: (String -> String) -> IO ()
interactLine f = loop
  where
    loop = do
      l <- getLine
      when (l /= "quit") $ putStrLn (f l) >> loop
like image 130
Zeta Avatar answered Oct 21 '22 21:10

Zeta