4
getText = do
    c <- getChar
    s <- getText
    return (c : s)

main = do
    s <- getText
    putStr s

What I expect to see is that the input line being echoed each time after I press 'Enter'. But nothing is echoed ... (I know that this is a infinite loop) It seems that it won't "return" until all the "IO" above it are performed. ...

However, the following code:

main = do
    s <- getContents
    putStr s

It display the line immediately after input.

Given the function getChar, can I write a getText that behaves like getContents?

RnMss
  • 3,696
  • 3
  • 28
  • 37
  • Just be aware that lazy IO can result in many problems that are hard to debug and solve. See [What's so bad about Lazy I/O?](http://stackoverflow.com/q/5892653/1333025) – Petr Feb 16 '13 at 21:20

3 Answers3

8

This is a job for ... unsafeInterleaveIO - the special action that makes lazy IO possible. It lets you turn an IO action into one that is bound to a thunk. This can then be stored in a structure, and the action will only evaluate when its result is demanded.

getText = unsafeInterleaveIO $ do
    c <- getChar
    s <- getText
    return (c : s)

Now your getText returns immediately with just a suspended computation for each getChar. If you need the result, it is run.

Don Stewart
  • 137,316
  • 36
  • 365
  • 468
7

This can be accomplished with the unsafeInterleaveIO function from System.IO.Unsafe. Your getText function then becomes

getText = do
    c <- getChar
    s <- unsafeInterleaveIO $ getText
    return (c : s)

Abstracting slightly, we can get a function to generalize this behaviour

lazyDoIO :: IO a -> IO [a]
lazyDoIO act = unsafeInterleaveIO $ do
    now <- act
    rest <- lazyDoIO act
    return (now : rest)

getText = lazyDoIO getChar

Most Haskellers would cringe at doing this, however. If you want to do incremental stream processing of IO generated data, it would be much safer to use a library like Pipes or Conduits.

sabauma
  • 4,243
  • 1
  • 23
  • 23
  • I am using it to pack some APIs into some more functional approach... (Because I don't like Reactive very much ...) – RnMss Feb 16 '13 at 16:12
  • @DonStewart how did you mean your comment? Would you recommend lazy IO o(ve)r iteratees/pipes in some cases? I'd say that just because something (lazy IO) has been used for many years doesn't mean it's a good solution to a problem... – Petr Feb 16 '13 at 21:18
  • 1
    I don't think you should 'cringe' from lazy IO -- it is remains idiomatic Haskell, composes naturally, and essential for well-rounded Haskell skill. – Don Stewart Feb 17 '13 at 12:22
  • 1
    It composes naturally as long as the kinds of I/O you are doing are simple (which is a goal to be strived for). But if things get complicated, e.g. updating a file rather than reading from one and writing to another, "natural" compositions will be horribly buggy. – luqui Feb 19 '13 at 22:34
1

You talk about Enter but you don't check it in your code.

Try this:

getText = do
    c <- getChar
    if (c == '\n')
       then return [c]
       else do
              s <- getText
              return (c : s)

main = do
    s <- getText
    putStr s
Gabriel Riba
  • 6,698
  • 2
  • 17
  • 19
  • It doesn't need to be checked manually: in most console, the line you are typing is stored in a line-buffer before it takes effect (so that you can use backspace or something to modify it), until you press the "Enter". – RnMss Feb 16 '13 at 16:09
  • but you cannot pretend {putStr s} being evaluated if you place an infinite loop to get the s value – Gabriel Riba Feb 16 '13 at 16:18
  • I have seen the unsafeInterleaveIO effect (in the Don Stewart proposal), but then the program does not terminate! – Gabriel Riba Feb 16 '13 at 16:32
  • until you close stdin with Ctrl-z – Gabriel Riba Feb 16 '13 at 16:35
  • adding after {putStr s} an extra {putStrLn "Done"} never gets printed – Gabriel Riba Feb 16 '13 at 16:44
  • I voted for this answer because it does exactly what the questioner asked. It handles the situation when there are some characters before linefeed in the buffer. The original code got "stuck" at getText. I think the use of *dirty* unsafe IO is unnecessary in this case. – David Unric Feb 16 '13 at 17:35
  • I'd add the inifinite loop can be handled by a caller function or the output echoing directly in the getText. – David Unric Feb 16 '13 at 17:43