`forever`:如何将信息转发到下一次迭代?
`forever`: How to forward information to next iteration?
队列中有一个线程在等待新输入以将其安全保存到文件系统。它还创建备份副本。 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 小时写入一次以上,那就太好了。在命令式编程中,我可能会在 saveThreadFunc
中的 "forever loop" 中使用可变的 int lastBackupTime
。在Haskell中如何实现同样的效果?
用显式递归替换 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
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)
向 monad 添加状态的常用方法是使用 transformers
包(Haskell 平台的一部分)中 Control.Monad.Trans.State.Strict
的 StateT
。在这种情况下,您将更改 saveThreadFunc
:
的类型
saveThreadFunc :: TQueue String -> StateT Int IO ()
你必须 Control.Monad.Trans.lift
实际的 IO
东西到 StateT Int IO
,然后最后 evalStateT
把整个东西变成 IO a
.
这种方法可能比 Tom Ellis 建议的 iterateM_
方法更模块化(尽管这是个人喜好问题),并且通常会比 chi 建议的 IORef
版本优化得更好你回避。
队列中有一个线程在等待新输入以将其安全保存到文件系统。它还创建备份副本。 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 小时写入一次以上,那就太好了。在命令式编程中,我可能会在 saveThreadFunc
中的 "forever loop" 中使用可变的 int lastBackupTime
。在Haskell中如何实现同样的效果?
用显式递归替换 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
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)
向 monad 添加状态的常用方法是使用 transformers
包(Haskell 平台的一部分)中 Control.Monad.Trans.State.Strict
的 StateT
。在这种情况下,您将更改 saveThreadFunc
:
saveThreadFunc :: TQueue String -> StateT Int IO ()
你必须 Control.Monad.Trans.lift
实际的 IO
东西到 StateT Int IO
,然后最后 evalStateT
把整个东西变成 IO a
.
这种方法可能比 Tom Ellis 建议的 iterateM_
方法更模块化(尽管这是个人喜好问题),并且通常会比 chi 建议的 IORef
版本优化得更好你回避。