`forever`: how to redirect information to the next iteration?

There is a thread waiting to enter new input into the queue for safe storage on the file system. It also backs up. sscce is as follows:

import Control.Concurrent import Control.Concurrent.STM import Control.Monad import Data.Time.Clock.POSIX main :: IO () main = do contentQueue <- atomically $ newTQueue _ <- forkIO $ saveThreadFunc contentQueue forever $ do line <- getLine atomically $ writeTQueue contentQueue line saveThreadFunc :: TQueue String -> IO () saveThreadFunc queue = forever $ do newLine <- atomically $ readTQueue queue now <- round `fmap` getPOSIXTime :: IO Int writeFile "content.txt" newLine -- todo: Backup no more than once every 86400 seconds (24 hours). backupContent now newLine backupContent :: Int -> String -> IO () backupContent t = writeFile $ "content.backup." ++ show t 

Now it would be great if the backup was not written more than once every 24 hours. In imperative programming, I would probably use the mutable int lastBackupTime inside the forever loop in saveThreadFunc . How can you achieve the same effect in Haskell?

+7
loops haskell monads
source share
3 answers

What about Control.Monad.Loops.iterateM_ ? This is a little tidier as it avoids the recursion explication.

 iterateM_ :: Monad m => (a -> ma) -> a -> mb saveThreadFunc :: TQueue String -> Int -> IO () saveThreadFunc queue = iterateM_ $ \lastBackupTime -> do newLine <- atomically $ readTQueue queue now <- round `fmap` getPOSIXTime :: IO Int writeFile "content.txt" newLine let makeNewBackup = now >= lastBackupTime + 86400 when makeNewBackup (backupContent now newLine) return (if makeNewBackup then now else lastBackupTime) 
+8
source share

Replace forever with explicit recursion.

 foo :: Int -> IO () foo n = do use n foo (n+1) 

Of course, you can use any type for your state instead of Int .

Otherwise, if you really want a variable state:

 foo :: IO () foo = do r <- newIORef (0 :: Int) forever $ do n <- readIORef r use n writeIORef r (n+1) 

If you really need variability for some other reason, I would not recommend the second option.


Adapting the specified idea to a specific code:

 saveThreadFunc :: Int -> TQueue String -> IO () saveThreadFunc lastBackupTime queue = do newLine <- atomically $ readTQueue queue now <- round `fmap` getPOSIXTime :: IO Int writeFile "content.txt" newLine let makeNewBackup = now >= lastBackupTime + 86400 if makeNewBackup then do backupContent now newLine saveThreadFunc now queue else saveThreadFunc lastBackupTime queue 
+2
source share

The usual way to add state to a monad is using StateT from Control.Monad.Trans.State.Strict in the transformers package (part of the Haskell platform). In this case, you change the saveThreadFunc type:

 saveThreadFunc :: TQueue String -> StateT Int IO () 

You need Control.Monad.Trans.lift actual IO stuff before StateT Int IO , and then at the end of evalStateT to turn it all into IO a .

This approach is perhaps more modular than iterateM_ , which Tom Ellis alone offers (although it is a bit of a taste) and will usually be optimized better than the IORef version that you avoid.

0
source share

All Articles