有一个线程正在等待队列中的新输入以将其安全地保存到文件系统。它还会创建备份副本。 sscce看起来像这样:
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
如果备份不会每24小时写一次以上,那就太棒了。在命令式编程中,我可能会在int lastBackupTime
中的“forever loop”中使用可变saveThreadFunc
。如何在Haskell中实现相同的效果?
答案 0 :(得分:8)
Control.Monad.Loops.iterateM_
怎么样?这略微润湿,因为它避免了明确的递归。
iterateM_ :: Monad m => (a -> m a) -> a -> m b
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)
答案 1 :(得分:2)
用显式递归替换forever
。
foo :: Int -> IO ()
foo n = do
use n
foo (n+1)
当然,您可以为州使用任何类型,而不是Int
。
否则,如果确实想要可变状态:
foo :: IO ()
foo = do
r <- newIORef (0 :: Int)
forever $ do
n <- readIORef r
use n
writeIORef r (n+1)
除非你因其他原因确实需要可变性,否则我不推荐第二种选择。
使上述想法适应具体的代码:
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 :(得分:0)
向monad添加状态的常用方法是使用StateT
包中Control.Monad.Trans.State.Strict
的{{1}}(Haskell平台的一部分)。在这种情况下,您可以更改transformers
的类型:
saveThreadFunc
您必须saveThreadFunc :: TQueue String -> StateT Int IO ()
实际Control.Monad.Trans.lift
到IO
的内容,然后最后StateT Int IO
将整个内容转换为evalStateT
}。
这种方法可能比汤姆·埃利斯所建议的IO a
更为模块化(虽然这是一种品味问题),并且通常会优于iterateM_
版本chi建议你避免。