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.