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?
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.