haskellfunctional-programmingio-monad

What is the most terse way of reading single keystrokes up to one specific key?


I had written the simple program below, convinced it would absorb all keystrokes I hit and, when I hit q, it would print the previous keys as a single string.

import System.IO
main = do
  hSetBuffering stdin NoBuffering
  hSetEcho stdin False
  x <- takeWhile (/= 'q') <$> sequence (repeat getChar)
  print x

But that's not the case.

It's not that keys are not read, as it's obvious when I change the longest line to

  x <- takeWhile (/= 'q') <$> sequence (repeat (getChar >>= \c -> print c >> return c))

which does print the characters one by one as I type them; but the q doesn't cause the loop to exit.

My understanding is that sequence is not lazy, in the sense that it will try to run the infinite list of getChar actions before takeWhile has a chance to start its job.

If that's the case, then what other options do I have?

If the intent is clear form the snippet above, I can add that in the real application getChar would be swapped with a function that does c <- getChar and then makes a processing on it.


Solution

  • You may consider a little helper action for that purpose:

    getInput :: Char -> IO Char
    getInput previous = do
      c <- getChar
      if c == 'q' then return previous else getInput c
    

    This would enable you to get x like

    x <- getInput ' '
    

    or with some other appropriate default value instead of ' '.

    If you truly want a one-liner, you can always use fix to convert any recursive action into a lambda expression.

    First, then,

    import Data.Function
    

    and then

    x <- (fix $ \rec previous -> getChar >>= \c -> if c == 'q' then return previous else rec c) ' '