listhaskellmonadslazy-evaluationlazy-sequences

Using the >>= and =<< operators to combine IO in Haskell


I am trying to run an "infinite" simulation, printing the results of each step.

There's a function nextFrameR which takes an input Map and steps forward the simulation to return an output Map, and then there's a render_ function which takes an input Map and prints some things to stdout, returning the input Map (so that I can use iterate or something like it).

I'm really struggling to put all these pieces together as am relatively new to Haskell. I found this answer extremely interesting but am not sure how to put it directly into practice due to the combination of the two functions (I have tried playing with liftM2 and iterate).

The type signatures are as follows:

nextFrameR :: Map -> IO Map
render_ :: Map -> IO Map -- originally Map -> IO ()

I'm not really sure where to go from here, I can do something like:

(iterate (>>= nextFrameR) initialMap) :: [IO Map]

But that just gives me an (infinite?) list of frames (I think), which is great, it just doesn't let me print them as I don't know how to combine the rendering function in there.


Solution

  • iterate works fairly well for non-IO computation, but if you are in IO you can't easily exploit iterate.

    To see why, your list

    (iterate (>>= nextFrameR) initialMap) :: [IO Map]
    

    is

    [ initialMap
    , initialMap >>= nextFrameR
    , initialMap >>= nextFrameR >>= nextFrameR
    ...
    

    so... how could we exploit that to have an infinite loop? We can't take the non-existent "last element". We also can't execute all the operations in that list in sequence, since that would run initialMap many times.

    It's easier if we avoid using iterate and resort to basics like recursion:

    loop :: Map -> IO ()
    loop m = do
       m' <- nextFrameR m
       render_ m'       -- it looks like you want this
       -- feel free to add some delay here, or some stopping condition to exit the loop
       loop m'
    
    main :: IO ()
    main = do
       m <- initialMap
       loop m
    

    You can turn the above code into some code which uses >>= but there is no need to.

    Finally, there's no need to make render_ return the same Map. You can make that return IO ().

    If you are a beginner, I'd recommend to initially stay away from "smart" library functions like mapM_, traverse, for, sequence, liftM2, ap, ... and learn to do everything using only do-blocks and recursion. Then, once you get how this works, you can try to improve your code exploiting the library helpers.