![](/img/trans.png)
[英]MonadBaseControl: how to lift simpleHTTP from Happstack?
[英]MonadBaseControl: how to lift ThreadGroup
在模塊Control.Concurrent.Thread.Group
threads包中有一個函數forkIO
:
forkIO :: ThreadGroup -> IO α -> IO (ThreadId, IO (Result α))
我想從monad-control使用MonadBaseControl
來解除它。 這是我的嘗試:
fork :: (MonadBase IO m) => TG.ThreadGroup -> m α -> m (ThreadId, m (Result α))
fork tg action = control (\runInBase -> TG.forkIO tg (runInBase action))
這是錯誤消息:
Couldn't match type `(ThreadId, IO (Result (StM m α)))'
with `StM m (ThreadId, m (Result α))'
Expected type: IO (StM m (ThreadId, m (Result α)))
Actual type: IO (ThreadId, IO (Result (StM m α)))
In the return type of a call of `TG.forkIO'
In the expression: TG.forkIO tg (runInBase action)
In the first argument of `control', namely
`(\ runInBase -> TG.forkIO tg (runInBase action))'
要使類型匹配需要更改的內容?
主要問題是IO a
forkIO
IO a
參數。 要在IO
分叉ma
動作,我們需要一種方法來將ma
運行到IO a
。 為此,我們可以嘗試制作具有runBase :: MonadBase bm => ma -> ba
方法的monad類,但很少有趣的變換器可以提供。 如果我們考慮例如StateT
轉換器,它可以弄清楚如果首先有機會觀察它自己的狀態,如何用runStateT
在base monad中運行一些東西。
runFork :: Monad m => StateT s m a -> StateT s m (m b)
runFork x = do
s <- get
return $ do
(a, s') <- runStateT x s
return a
這表明類型為runForkBase :: MonadBase bm => ma -> m (ba)
,我們將為以下類型類決定。
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
import Control.Monad.Base
class (MonadBase b m) => MonadRunForkBase b m | m -> b where
runForkBase :: m a -> m (b a)
我在名稱中添加了Fork
這個詞,以強調未來的狀態變化通常不會在兩個期貨之間分享。 出於這個原因,像WriterT
這樣可以提供runBase
的少數有趣的變換器只提供了一個無趣的runBase
; 它們會產生永遠不會被觀察到的副作用。
我們可以使用由MonadRunForkBase IO m
實例提供的有限降低形式來編寫類似fork
的東西。 我要lift
正常forkIO
從底部而不是從一個線程 ,你可以做同樣的方式。
{-# LANGUAGE FlexibleContexts #-}
import Control.Concurrent
forkInIO :: (MonadRunForkBase IO m) => m () -> m ThreadId
forkInIO action = runForkBase action >>= liftBase . forkIO
這提出了一個問題,“我們可以為MonadRunForkBase
實例提供哪些變換器”? 直接蝙蝠,我們可以輕松地為任何具有MonadBase
實例的基礎monad提供它們
import Control.Monad.Trans.Identity
import GHC.Conc.Sync (STM)
instance MonadRunForkBase [] [] where runForkBase = return
instance MonadRunForkBase IO IO where runForkBase = return
instance MonadRunForkBase STM STM where runForkBase = return
instance MonadRunForkBase Maybe Maybe where runForkBase = return
instance MonadRunForkBase Identity Identity where runForkBase = return
對於變形金剛來說,通常更容易構建這樣的功能。 這是可以在直接底層monad中運行fork的變換器類。
import Control.Monad.Trans.Class
class (MonadTrans t) => MonadTransRunFork t where
runFork :: Monad m => t m a -> t m (m a)
我們可以提供默認實現,以便在基礎中一直運行
runForkBaseDefault :: (Monad (t m), MonadTransRunFork t, MonadRunForkBase b m) =>
t m a -> t m (b a)
runForkBaseDefault = (>>= lift . runForkBase) . runFork
這讓我們完成了MonadRunForkBase
例如StateT
分兩步進行。 首先,我們將使用runFork
從上面做出MonadTransRunFork
實例
import Control.Monad
import qualified Control.Monad.Trans.State.Lazy as State
instance MonadTransRunFork (State.StateT s) where
runFork x = State.get >>= return . liftM fst . State.runStateT x
然后我們將使用默認值來提供MonadRunForkBase
實例。
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
instance (MonadRunForkBase b m) => MonadRunForkBase b (State.StateT s m) where
runForkBase = runForkBaseDefault
我們可以為RWS
做同樣的事情
import qualified Control.Monad.Trans.RWS.Lazy as RWS
instance (Monoid w) => MonadTransRunFork (RWS.RWST r w s) where
runFork x = do
r <- RWS.ask
s <- RWS.get
return $ do
(a, s', w') <- RWS.runRWST x r s
return a
instance (MonadRunForkBase b m, Monoid w) => MonadRunForkBase b (RWS.RWST r w s m) where
runForkBase = runForkBaseDefault
與我們在MonadRunForkBase
中開發的MonadBaseControl
, monad-control中的MonadBaseControl
並未假設“未來的狀態變化通常不會在兩個期貨之間共享”。 MonadBaseContol
和control
努力使用restoreM :: StM ma -> ma
從控制結構中的分支恢復狀態。 對於forkIO
,這不會產生問題; 使用forkIO
是MonadBaseControl
文檔中提供的示例。 由於返回了額外的m (Result a)
,這對於forkIO
是一個小問題。
我們想要的m (Result a)
實際上將作為IO (Result (StM ma))
。 我們可以擺脫IO
並用帶有liftBase
的m
替換它,留下m (Result (StM ma))
。 我們可以將StM ma
轉換為恢復狀態的ma
,然后使用restoreM
返回a
,但它被卡在Result ~ Either SomeException
。 Either l
是restoreM
函數,所以我們可以在其中的任何地方應用restoreM
,將類型簡化為m (Result (ma))
。 Either l
也是Traversable
,並且對於任何Traversable
t
我們總是可以將其交換一個內部Monad
或Applicative
與sequenceA :: t (fa) -> f (ta)
在這種情況下,我們可以使用專用mapM
,它是fmap
和sequenceA
的組合,只有Monad
約束。 這將給出m (m (Result a))
,並且m
s將通過Monad中的連接或僅使用>>=
。 這導致了
{-# LANGUAGE FlexibleContexts #-}
import Control.Concurrent
import Control.Concurrent.Thread
import qualified Control.Concurrent.Thread.Group as TG
import Control.Monad.Base
import Control.Monad.Trans.Control
import Data.Functor
import Data.Traversable
import Prelude hiding (mapM)
fork :: (MonadBaseControl IO m) =>
TG.ThreadGroup -> m a -> m (ThreadId, m (Result a))
fork tg action = do
(tid, r) <- liftBaseWith (\runInBase -> TG.forkIO tg (runInBase action))
return (tid, liftBase r >>= mapM restoreM)
當我們在原始線程中運行m (Result a)
時,它會將狀態從分叉線程復制到原始線程,這可能很有用。 如果要在讀取Result
后恢復主線程的狀態,則需要先捕獲它。 checkpoint
將捕獲整個狀態並返回一個動作來恢復它。
checkpoint :: MonadBaseControl b m => m (m ())
checkpoint = liftBaseWith (\runInBase -> runInBase (return ()))
>>= return . restoreM
一個完整的例子將展示兩個線程對狀態的影響。 無論在另一個線程中修改狀態的努力如何,兩個線程都從fork
發生時獲得狀態。 當我們在主線程中等待結果時,主線程中的狀態被設置為分叉線程中的狀態。 我們可以通過運行checkpoint
創建的操作來獲取主線程的狀態。
import Control.Monad.State hiding (mapM)
example :: (MonadState String m, MonadBase IO m, MonadBaseControl IO m) => m ()
example = do
get >>= liftBase . putStrLn
tg <- liftBase TG.new
(_, getResult) <- fork tg (get >>= put . ("In Fork:" ++) >> return 7)
get >>= put . ("In Main:" ++)
revert <- checkpoint
result <- getResult
(liftBase . print) result
get >>= liftBase . putStrLn
revert
get >>= liftBase . putStrLn
main = do
runStateT example "Initial"
return ()
這輸出
Initial
Right 7
In Fork:Initial
In Main:Initial
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.