在模块threads的Control.Concurrent.Thread.Group
包中有一个函数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))'
要使类型匹配需要更改的内容?
答案 0 :(得分:2)
主要问题是IO a
的{{1}}参数。要在forkIO
中分配m a
操作,我们需要一种方法来运行IO
到m a
。要做到这一点,我们可以尝试制作具有IO a
方法的monad类,但很少有趣的变换器可以提供。如果我们考虑例如runBase :: MonadBase b m => m a -> b a
变换器,如果它首先有机会观察它自己的状态,那么它可以弄清楚如何在基础monad中用StateT
运行某些东西。
runStateT
这表明类型为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 b m => m a -> m (b a)
我在名称中添加了{-# 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
实例提供的有限降级形式的任何内容,我们都可以编写fork
之类的内容。我从MonadRunForkBase IO m
开始lift
从forkIO
开始,而不是来自threads的{-# LANGUAGE FlexibleContexts #-}
import Control.Concurrent
forkInIO :: (MonadRunForkBase IO m) => m () -> m ThreadId
forkInIO action = runForkBase action >>= liftBase . forkIO
,你也可以这样做。
MonadRunForkBase
这提出了一个问题,&#34;我们可以为&#34;提供哪些变形金刚MonadBase
个实例?直接蝙蝠,我们可以为任何具有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
个实例
import Control.Monad.Trans.Class
class (MonadTrans t) => MonadTransRunFork t where
runFork :: Monad m => t m a -> t m (m a)
对于变形金刚来说,通常更容易构建这样的功能。这是变形金刚的类,它可以在直接的底层monad中运行一个分支。
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中的MonadBaseContol
并未在假设中出现过&#34;未来的状态变化不会出现在一般在两个期货之间共享&#34;。 control
和restoreM :: StM m a -> m a
努力通过forkIO
恢复控制结构中的分支状态。这不会给forkIO
基地带来问题;使用MonadBaseControl
是m (Result a)
文档中提供的示例。这对于forkIO
from threads来说只是一个小问题,因为返回了额外的m (Result a)
。
我们想要的IO (Result (StM m a))
实际上会以IO
的形式返回。我们可以删除m
并将liftBase
替换为m (Result (StM m a))
,并将StM m a
留给我们。我们可以将m a
转换为恢复状态的a
,然后使用restoreM
返回Result ~ Either SomeException
,但它会卡在Either l
内。 restoreM
是一个仿函数,因此我们可以在其中的任何位置应用m (Result (m a))
,将类型简化为Either l
。 Traversable
也是Traversable
,对于任何t
Monad
,我们始终可以在Applicative
或sequenceA :: t (f a) -> f (t a)
内与mapM
进行交换。在这种情况下,我们可以使用特殊用途fmap
,它是sequenceA
和Monad
的组合,只有m (m (Result a))
约束。这将提供m
,并且>>=
将通过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
此输出
{{1}}