Tickers in Haskell

Timers are for when you want to do something once in the future - tickers are for when you want to do something repeatedly at regular intervals. Here’s an example of a ticker that ticks periodically until we stop it.

import Control.Concurrent (forkIO, threadDelay)
import Control.Monad (forever)
import Data.Time.Clock (getCurrentTime)
import System.IO (hFlush, stdout)

main :: IO ()
main = do
    -- Tickers use a similar mechanism to timers: a
    -- channel that is sent values. Here we'll use an
    -- MVar to simulate the channel and communicate
    -- between threads.
    (tickerThread, stopTicker) <- newTicker 500000 -- 500ms in microseconds

    -- We'll stop our ticker after 1600ms.
    threadDelay 1600000
    stopTicker
    putStrLn "Ticker stopped"

newTicker :: Int -> IO (IO (), IO ())
newTicker interval = do
    done <- newEmptyMVar
    let ticker = forever $ do
            threadDelay interval
            time <- getCurrentTime
            putStrLn $ "Tick at " ++ show time
            hFlush stdout
            threadDelay 1  -- yield to other threads

    thread <- forkIO $ ticker `finally` (void $ tryPutMVar done ())
    
    let stop = do
            putMVar done ()
            takeMVar done  -- ensure the thread has finished

    return (thread, stop)

When we run this program the ticker should tick 3 times before we stop it.

$ runhaskell tickers.hs
Tick at 2023-05-25 10:15:30.123456 UTC
Tick at 2023-05-25 10:15:30.623789 UTC
Tick at 2023-05-25 10:15:31.124567 UTC
Ticker stopped

In this Haskell version, we use forkIO to create a new thread that simulates the ticker. We use threadDelay to pause the execution for a specified amount of time, similar to time.Sleep in Go.

The newTicker function returns a pair of IO actions: one to start the ticker thread and another to stop it. This is similar to how Go’s time.NewTicker returns a Ticker struct with a Stop method.

We use getCurrentTime from the Data.Time.Clock module to get the current time, which we print with each tick.

The MVar type is used to communicate between threads and to signal when the ticker should stop, similar to how channels are used in the Go version.

Note that Haskell doesn’t have a direct equivalent to Go’s select statement. In this example, we’ve structured the code differently to achieve similar behavior.