`forever`:如何将信息转发到下一次迭代?

时间:2015-02-12 12:15:20

标签: loops haskell monads

有一个线程正在等待队列中的新输入以将其安全地保存到文件系统。它还会创建备份副本。 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中实现相同的效果?

3 个答案:

答案 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.liftIO的内容,然后最后StateT Int IO将整个内容转换为evalStateT }。

这种方法可能比汤姆·埃利斯所建议的IO a更为模块化(虽然这是一种品味问题),并且通常会优于iterateM_版本chi建议你避免。