0

Trying to figure out best practices to handle repeated actions in Haskell using Control.Concurrent.Timer. Can't find good examples.

Suppose I have repeated action that must be done in application. Say it's fetching data from some webpage.

import Control.Concurrent.Suspend.Lifted 
import Control.Concurrent.Timer

main :: IO ()
main = do
  url <- fetchUrl :: IO Url
  doSomethingWithUrl url

and I would like url to be fetched repeatly inside main so the data is always fresh. How could it be done?

2
  • How are you going to consume the "fresh" data? Immediately after fetch? If so you can just forever $ threadDelay time >> forkIO (fetchUrl >>= doSomethingWithUrl). On the other hand, if doSomething is async then you'll want some mutable memory that fetch updates and doSomething reads. Commented Aug 22, 2019 at 18:09
  • @ThomasM.DuBuisson Thanks for your interest. In my case doSomething is Scotty web server loop (scotty $ do ...) so i guess it's async. The data consumed after fetch. fetchUrl which is API call of some service in my case has restriction on calls per IP per time.
    – shegeley
    Commented Aug 22, 2019 at 18:29

1 Answer 1

1

Your needs sound like they fit with an IORef or MVar. Typed and not tested code is:

import Data.IORef (readIORef, writeIORef, newIORef)
import Control.Concurrent (threadDelay,forkIO)
import Control.Monad (void)

main :: IO ()
main = do
  ref <- newIORef mempty {- or default value -}
  _ <- forkIO (updatePeriodically fetchUrl ref)
  scotty $ do
        liftIO (readIORef ref) >>= etcEtc

updatePeriodically :: (IO SomeType) -> IORef SomeType -> IO ()
updatePeriodically op ref =
    let update = op >>= writeIORef ref
    forever $ threadDelay periodInMicroSeconds >> void (forkIO update)

This isn't perfect, if one of the fetchUrl calls is badly delayed then its write could clobber a newer call that returned quickly. That's probably a minor concern but you could handle it with a counter and compare and swap operation.

2
  • Thanks for your help again but I'd appreciate if I could see example with given (Control.Concurrent.Timer) lib. The part I can't figure out in it is the IO TimerIO in repeatedTimer :: IO () -> Delay -> IO TimerIO. In your example updatePeriodically has IO () in the end and it's wrapped in forever loop. I don't understand much of more-or-less «low-level» stuff: IORed, forkIO etc.
    – shegeley
    Commented Aug 23, 2019 at 1:06
  • Ok. I figured out that code to print "Hello" periodically might look like this using this libriary: ``` import Control.Monad import Control.Concurrent.Suspend.Lifted import Control.Concurrent.Timer printHelloPeriodically :: IO () printHelloPeriodically = do void $ repeatedTimer (putStrLn "Hello") (sDelay 5) main :: IO () main = do printHelloPeriodically ``` But how to deal with, say getLine (fetchUrl) and then print it periodically I can't understand
    – shegeley
    Commented Aug 23, 2019 at 8:11

Not the answer you're looking for? Browse other questions tagged or ask your own question.