使用forkFinally和WriterT

时间:2013-12-24 22:01:39

标签: haskell

我有一个等待时间的计时器功能和一个开始时间,并在结束时返回当前的UTCTime:

runTimer :: NominalDiffTime -> UTCTime -> IO UTCTime

我想在一个单独的线程中运行这个计时器功能,因为我想同时能够捕获用户的输入。在这个主循环中,我还使用WriterT monad转换器来记录事件。我也想在计时器正常结束时记录一个事件,所以我尝试使用forkFinally:

type Log = [Entry]           
data Entry = Entry {
      _etype :: EntryType    
    , _etime :: UTCTime      
} deriving Show

data EntryType = Start | End | Pause | Quit deriving Show

type Program = WriterT Log IO

loop :: Maybe ThreadId -> Program ()
loop timerId = do
  liftIO $ putStr ">"
  x <- liftIO $ getChar      
  now <- liftIO $ getCurrentTime
  case x of
    'q' -> do
            liftIO $ putStrLn "\n Quitting..."
            tell [Entry Quit now]
            -- Kill the timer thread if there is one.
            maybe (return ()) (liftIO . killThread) timerId
            liftIO $ putStrLn "\n Quit."
    's' -> case timerId of
             Just _ -> do
                 liftIO $ putStrLn "Timer already started!"
                 loop timerId
             Nothing -> do
                 -- Start a timer in a new thread.
                 tell [Entry Start now]
                 timerId' <- liftIO $ forkFinally (runTimer 5 now) eHandler
                 -- Call the main loop with the timer thread id.
                 loop (Just timerId')
    _   -> do
            liftIO $ putChar x
            loop timerId                       

eHandler :: (Show a, Exception e) => Either e a -> IO ()
eHandler (Left x) = throw x  
eHandler (Right x) = do
  putStrLn $ show x

eHandler' :: (Exception t) => Either t UTCTime -> WriterT Log IO ()
eHandler' (Left x) = throw x
eHandler' (Right t) = do
  tell [Entry End t]

当使用forkFinally和eHandler时,此代码可以正常工作。但是我如何让它与eHandler'一起使用,以便我可以在计时器结束时进行记录?

1 个答案:

答案 0 :(得分:1)

您需要将WriterT monad转换为IO monad,如下所示:

wrapWriterT::WriterT a IO c->IO c
wrapWriterT writer = do
  (result, log) <- runWriterT writer
  <do something with the log, like send it to a file>
  return result

然后像这样使用它:

timerId' <- liftIO $ forkFinally (runTimer 5 now) (wrapWriterT  . eHandler')

这里发生了什么?请记住,Writer monad用于传递额外的字符串,可以附加到日志中。 IO monad用于...... IOforkFinally需要输入IO(),但WriterT IO也会返回日志。 forkFinally不知道如何处理这个日志,但是如果你将它包装在一个函数中来提取日志并对它做一些事情,那么你只剩下一个IO monad,{ {1}}知道如何处理。


我确实想问一下...... forkFinally中的标准计时器函数是非阻塞的(你把它交给IO()类型的函数,然后返回,理解它将在以后完成)。你在做什么需要明确的线程?