Haskell: read input character from console immediately, not after newline
Asked Answered
I

6

33

I've tried this:

main = do
    hSetBuffering stdin NoBuffering 
    c <- getChar

but it waits until the enter is pressed, which is not what I want. I want to read the character immediately after user presses it.

I am using ghc v6.12.1 on Windows 7.

EDIT: workaround for me was moving from GHC to WinHugs, which supports this correctly.

Inquest answered 6/6, 2010 at 11:14 Comment(3)
That's not really a good workaround. The real workaround is to explicitly choose character-buffered IO; by using the getch from the system conio.h. Artelius' link contains example code for that.Cannoneer
It's a good workaround if it suits him! Using a different tool which doesn't have the bug makes sense. If you don't need the language features of recent ghc compilers, WinHugs is faster than ghci or winghci in my experience. It works without fuss and looks nicer. You also don't need to :r when you've edited your code, which I love.Monteverdi
How does it relate to main = do hSetBuffering stdin NoBuffering; interact $ map Data.Char.toUpper? In my case it waits for a new line before any output appears. (Ubuntu, GHC 7.6.3.)Portwine
D
21

Might be a bug:

http://hackage.haskell.org/trac/ghc/ticket/2189

The following program repeats inputted characters until the escape key is pressed.

import IO
import Monad
import Char

main :: IO ()
main = do hSetBuffering stdin NoBuffering
          inputLoop

inputLoop :: IO ()
inputLoop = do i <- getContents
               mapM_ putChar $ takeWhile ((/= 27) . ord) i

Because of the hSetBuffering stdin NoBuffering line it should not be necessary to press the enter key between keystrokes. This program works correctly in WinHugs (sep 2006 version). However, GHC 6.8.2 does not repeat the characters until the enter key is pressed. The problem was reproduced with all GHC executables (ghci, ghc, runghc, runhaskell), using both cmd.exe and command.com on Windows XP Professional...

Does answered 6/6, 2010 at 11:21 Comment(0)
M
23

Yes, it's a bug. Here's a workaround to save folks clicking and scrolling:

{-# LANGUAGE ForeignFunctionInterface #-}
import Data.Char
import Foreign.C.Types
getHiddenChar = fmap (chr.fromEnum) c_getch
foreign import ccall unsafe "conio.h getch"
  c_getch :: IO CInt

So you can replace calls to getChar with calls to getHiddenChar.

Note this is a workaround just for ghc/ghci on Windows. For example, winhugs doesn't have the bug and this code doesn't work in winhugs.

Monteverdi answered 13/11, 2012 at 22:52 Comment(2)
I like this solution. Note, however, that unsafe will cause a blocking call to getch to block all other threads in the program. I am author of the hidden-char package on Hackage which has a variant of this function using the unsafe FFI.Avalon
If you get something similar to this ByteCodeLink: can't find label During interactive linking, GHCi couldn't find the following symbol: getch in GHCi, try _getch instead of getch for the foreign import. Source: stackoverrun.com/de/q/10551494December
D
21

Might be a bug:

http://hackage.haskell.org/trac/ghc/ticket/2189

The following program repeats inputted characters until the escape key is pressed.

import IO
import Monad
import Char

main :: IO ()
main = do hSetBuffering stdin NoBuffering
          inputLoop

inputLoop :: IO ()
inputLoop = do i <- getContents
               mapM_ putChar $ takeWhile ((/= 27) . ord) i

Because of the hSetBuffering stdin NoBuffering line it should not be necessary to press the enter key between keystrokes. This program works correctly in WinHugs (sep 2006 version). However, GHC 6.8.2 does not repeat the characters until the enter key is pressed. The problem was reproduced with all GHC executables (ghci, ghc, runghc, runhaskell), using both cmd.exe and command.com on Windows XP Professional...

Does answered 6/6, 2010 at 11:21 Comment(0)
L
4

Hmm.. Actually I can't see this feature to be a bug. When you read stdin that means that you want to work with a "file" and when you turn of buffering you are saying that there is no need for read buffer. But that doesn't mean that application which is emulating that "file" should not use write buffer. For linux if your terminal is in "icanon" mode it doesn't send any input until some special event will occur (like Enter pressed or Ctrl+D). Probably console in Windows have some similar modes.

Latreshia answered 6/6, 2010 at 11:46 Comment(3)
Thank you. Sounds truthfully, but if you see the bug description: it is really exaclty what I was asking, so for now I am going to mark answer of Artelius.Inquest
At least it works differently under Windows ans Linux. Under linux the code posted by Steves works WITHOUT waiting. So I think it's a bug, and should be fixed.Detective
This is (partially) incorrect. When using Console Emulation Software, or the MinGW console, buffering is still ignored. Windows ignores buffering at OS level, not at console level.Supertanker
G
4

The Haskeline package worked for me.

If you need it for individual characters, then just change the sample slightly.

  1. getInputLine becomes getInputChar
  2. "quit" becomes 'q'
  3. ++ input becomes ++ [input]
main = runInputT defaultSettings loop
    where 
        loop :: InputT IO ()
        loop = do
            minput <- getInputChar "% "
            case minput of
                Nothing -> return ()
                Just 'q' -> return ()
                Just input -> do outputStrLn $ "Input was: " ++ [input]
                                 loop
Gracegraceful answered 25/9, 2013 at 2:11 Comment(0)
E
0

I used the haskeline package, suggested in other answers, to put together this simple alternative to getChar. It requests input again in the case that getInputChar returns Nothing. This worked for me to get past the issue; modify as needed.

import System.Console.Haskeline
  ( runInputT
  , defaultSettings
  , getInputChar
  )

betterInputChar :: IO Char
betterInputChar = do
  mc <- runInputT defaultSettings (getInputChar "")
  case mc of
    Nothing -> betterInputChar
    (Just c) -> return c
Eachelle answered 11/6, 2022 at 14:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.