2

I will try to state this question as short and to the point as possible.

I expected this computation to complete after inputting three characters to the command line. However, it does not. I am curious as to why this is.

return . take 3 =<< sequence (repeat getChar)

Edit:

I should add that I am trying to separate selection from generation and therefore wish to produce an infinite stream of characters that I select from.

Magnus Kronqvist
  • 1,559
  • 12
  • 22

2 Answers2

7

I/O sequences actions deterministically, so what you are really saying is that you want to execute the getChar action an infinite number of times and after that has finished, you want to take the three first items from the generated list. You can probably see how this will take a long time.

If you want to perform I/O lazily (and non-deterministically), you can do so using the function unsafeInterleaveIO from the System.IO.Unsafe package.

One way to implement what you want is, for example, like this:

import System.IO.Unsafe

lazyIOSequence :: [IO a] -> IO [a]
lazyIOSequence [] = return []
lazyIOSequence (x:xs) = do
    first <- unsafeInterleaveIO $ x
    rest  <- unsafeInterleaveIO $ lazyIOSequence xs
    return $ first:rest

test = return . take 3 =<< lazyIOSequence (repeat getChar)

However, the now the actual prompting of data from the user gets delayed until you evaluate elements of the list, so it might not behave as you'd like. In fact, lazy I/O streams are in general considered problematic (see e.g. the questions "Haskell lazy I/O and closing files" and "What's so bad about Lazy I/O?").

The currently recommended way to separate production from selection and processing is to use one of the various strict, stream-processing libraries such as enumerator, pipes or conduit.

Community
  • 1
  • 1
shang
  • 24,642
  • 3
  • 58
  • 86
  • So if I want to read characters from a stream until a given pattern is found, how should I do that? (hence, this is why I wanted to separate generation from selection) – Magnus Kronqvist Jul 25 '12 at 13:30
  • 2
    What do you mean by a "stream" in this context? If you read data from a file with `readFile` or from a handle using `hGetContents` the stream is automatically lazy since the above functions use `unsafeInterleaveIO` internally. However, lazy I/O is considered problematic for various reasons, so the currently recommended solution for separating generation and selection is to use a library such as [enumerator](http://hackage.haskell.org/package/enumerator), [pipes](http://hackage.haskell.org/package/pipes) or [conduit](http://hackage.haskell.org/package/conduit). – shang Jul 25 '12 at 13:46
  • Not sure I myself know what I mean here :) I did not know that those functions uses `unsafeInterleaveIO` under the hood. I need to do some reading up. Thanks! – Magnus Kronqvist Jul 25 '12 at 13:51
6
  1. Effects to the right of =<< complete before effects to the left of =<< begin.
    • This means that if the action to the right of =<< would run forever then the entire action will run forever.
  2. sequence takes a list of actions and gives back an action that performs all those actions.
    • If you give sequence an infinite list of actions, it will run forever.
  3. repeat takes a value, and gives an infinite list of that value, repeated.

Do you see now why you get the behaviour you experience?


This would work, because there is no infinite list:

sequence (take 3 $ repeat getChar)

But presumably your real selection function is more complicated than "take the first three". What is it?

dave4420
  • 46,404
  • 6
  • 118
  • 152
  • 1
    `take 3 $ repeat getChar` can be written as `replicate 3 getChar` – Squidly Jul 25 '12 at 13:13
  • Also, consider this: if you had written `take 4` instead of `take 3`, the user would according to you have been asked four times instead of three to input a character. That is, pure code would have been able to cause side-effects. You *can* allow this behavior, by using `unsafeInterleaveIO`. – dflemstr Jul 25 '12 at 13:14
  • Basically what I intend to do in the end is reading characters not from terminal but from a socket where I want to use a takeWhile until I see the wanted pattern. – Magnus Kronqvist Jul 25 '12 at 13:28
  • @MrBones Yes but `replicate 3 getChar` does not separate generation from selection since you generate the right amount from the beginning. – Magnus Kronqvist Jul 25 '12 at 13:45
  • @dflemstr How do you mean that this differs from `sequence (take 3 $ repeat getChar)` is pure code having a side effect here? – Magnus Kronqvist Jul 25 '12 at 13:48
  • 1
    In your example, a list of `getChar` actions is built up and THEN executed. This means that the actions are performed entirely in the IO monad; the number of actions is simply an input to the monadic code. In the non-deterministic example, the monadic code will be performed more or less often depending on how many values the pure code "consumes", which is completely different behavior. – dflemstr Jul 25 '12 at 15:08