Search code examples
haskellfunctional-programmingio

Simple counter in IO


I'm trying to create a simple counter which increases by 1 indefinitely, using IO.

I've been scratching my head ever since...

Ideally, I would like to do something along the lines of

tick = do putStr (counter)
          counter + 1
    where counter = 0

Then repeat the process. Then repeat the first 2 expressions. Or something along the lines of:

tick = tick'
       where 
           counter = 1
           tick' counter | counter > 0 = do putStrLn (show counter)
                                            tick' (counter + 1)
                         | otherwise = tick

Which gives me errors :/

Any help is appreciated :)


Solution

  • There are a couple ways to do this without using a mutable cell. You already did it with your second attempt, there's just a little error. You need to pass an initial value to the tick' function, not "set it" (haskell does not have an idea of assigning variables -- only definitions. If the line x = y appears, x will be y for its entire lifetime).

    tick = tick' 0
        where ...
    

    The counter = 0 line is not doing anything; it is defining a name that is never used. The counter used in the tick' function is bound as one of its arguments (and shadows the one defined to be 0). Take some time to stare at it with that in mind, see if that makes sense.

    There is a nice "higher order" way we can do this too. Essentially we want to run the infinitely long block of code:

    do
        print 0
        print 1
        print 2
        ...
    

    There is a function called sequence :: [IO a] -> IO [a] (see caveat below) that will take a list of actions and construct an action. So if we can construct the list [print 0, print 1, print 2, ...] then we can pass it to sequence to build the infinitely long block we are looking for.

    Take note here, this is a very important concept in Haskell: [print 0, print 1, print 2] does not print those three numbers then construct the list [0,1,2]. Instead it is itself a list of actions, whose type is [IO ()]. Making the list does nothing; it is only when you bind an action to main that it will be executed. For example, we might say:

    main = do
        let xs = [putStrLn "hello", getLine >> putStrLn "world"]
        xs !! 0
        xs !! 0
        xs !! 1
        xs !! 1
        xs !! 0
    

    This would twice print hello, twice get a line and print world after each, then once print hello again.

    With that concept, it is easy to build the list of actions [print 0, print 1, ...] with a list comprehension:

    main = sequence [ print x | x <- [0..] ]
    

    We can simplify a bit:

    main = sequence (map (\x -> print x) [0..])
    main = sequence (map print [0..])
    

    So map print [0..] is the list of actions [print 0, print 1, ...] we were looking for, then we just pass that to sequence which chains them together.

    This pattern of sequence is common, and has its own mapM:

    mapM :: (a -> IO b) -> [a] -> IO [b]
    mapM f xs = sequence (map f xs)
    

    Thus:

    main = mapM print [0..]
    

    About as simple as you could want.

    One note about performance: since we are not using the output of these functions, we should be using sequence_ and mapM_, with trailing underscores, which are optimized for that purpose. Usually this wouldn't matter in a Haskell program because of garbage collection, but in this particular use case is kind of a special case because of various subtleties. You'll find that without the _s, the memory usage of your program gradually grows as the list of results (in this case [(),(),(),...]) is constructed but never used.

    Caveat: I have given the type signatures of sequence and mapM specialized to IO, not a general monad, so that the reader does not have to learn about the orthogonal concepts of actions having types and typeclasses at the same time.