Search code examples
haskellprintingiolazy-evaluation

How can I print every item in an infinite list with proper IO monad


I have this simple definition of the integers, done with an infinite list:

nats = 0: map (+1) nats

And I would like to make a version of that infinite list that print every element.

I did it with Data.Trace (trace)

nats = 0: map (\x -> 1 + trace (show x) x) nats

But it's a bit cheating. It would be nicer to do it properly with the IO monad.

I tried to do it with an infinite list of type IO [Int] with the following code:

nats' = (fmap f nats') >>= mapM (\x -> do{print x; return x})
  where f l = 0:map (+1) l

The type signatures work, but however when I try to evaluate it with take 10 'nats, it gets stuck in an infinite recursion without printing anything.

What is wrong with this code, is the type signature right and what would be the right way to approach this problem?

EDIT :

I have a variant of this problem where I want to create an infinite list containing all the inputs of the user. How can I do that?


Solution

  • The IO monad is strict. When we run x <- ioAction, we fully execute ioAction and observe all its side effects before x is bound to a value. Consequently, if ioAction is an infinite loop, it will never return a value to be bound to to x.

    In your case, the code has the form

    nats' = (fmap f nats') >>=  ...
    

    which is equivalent to

    nats' = nats' >>= return . f >>= ...
    

    So, nats' will recurse before executing return . f, causing an infinite loop and preventing any output.

    How to make this actually work?

    The most idiomatic Haskell way is to separate side effects from actual computation: we define a pure value

    nats = 0 : map (+1) nats
    

    and the we print it

    nats' = for_ nats print
    

    Note that above there is little point to use for/traverse/mapM (instead of their _ variants), since the list is infinite so it will never return a value, but only perform infinitely many prints.

    If you really want to interleave IO and computation, even if that's usually less idiomatic, you can use recursion:

    nats' = go 0
      where go l = print l >>= go (l+1)
    

    If you want to try returning a list anyways, even if that will actually never happen, you can use this:

    nats' = go 0
      where go l = do 
               print l
               ls <- go (l+1)
               return (l:ls)
    

    However, this is unnecessarily complex.