MonadBaseControl:如何解除ThreadGroup [英] MonadBaseControl: how to lift ThreadGroup
问题描述
在模块线程包中。 org / package / threads / docs / Control-Concurrent-Thread-Group.htmlrel =nofollow> Control.Concurrent.Thread.Group
一个函数 forkIO
:
forkIO :: ThreadGroup - > IOα - > IO(ThreadId,IO(Resultα))
我想用 MonadBaseControl
from 单子控制。这是我的尝试:
fork ::(MonadBase IO m)=> TG.ThreadGroup - > mα - > m(ThreadId,m(Resultα))
fork tg action = control(\ runInBase - > TG.forkIO tg(runInBase action))
这里是错误消息:
无法匹配类型'(ThreadId,IO(Result(StM mα)))'
with'StM m(ThreadId,m(Resultα))'
预期类型:IO(StM m(ThreadId,m a)))
实际类型:IO(ThreadId,IO(Result(StM mα)))
在`TG.forkIO'调用的返回类型中
在表达式中: TG.forkIO tg(runInBase action)
在`control'的第一个参数中,即
`(\ runInBase - > TG.forkIO tg(runInBase action))'
如何改变类型以匹配?
主要问题在于 forkIO
的 IO a
参数。要在 IO
中分支 ma
动作,我们需要一种方法来运行 ma
到 IO a
。要做到这一点,我们可以尝试使单元的类具有 runBase :: MonadBase b m => m a - > b a
方法,但很少有趣的变形金刚可以提供这种方法。例如,如果我们考虑 StateT
转换器,它可以计算出如何使用 runStateT
在基本monad中运行if它首先有机会观察自己的状态。
runFork :: Monad m => StateT s m a - > StateT sm(mb)
runFork x = do
s< - get
return $ do
(a,s')< - runStateT xs
return a
这表明类型 runForkBase :: MonadBase bm => m a - > m(ba)
,我们将在下面的类型类中解决它。
{ - #语言MultiParamTypeClasses# - }
{ - #LANGUAGE FunctionalDependencies# - }
import Control.Monad.Base
class(MonadBase bm)=> MonadRunForkBase b m | m - > b其中
runForkBase :: m a - > m(ba)
我添加了单词 Fork
来强调,未来的状态变化通常不会在两种期货之间共享。出于这个原因,像 WriterT
这样可能提供了 runBase
的少数感兴趣的转换器只提供了一个不感兴趣的 runBase
;他们产生的副作用永远不会被观察到。
对于任何具有有限形式的东西,我们可以写一些像 fork
由 MonadRunForkBase IO m
实例提供的降低。我将从 forkIO
/hackage.haskell.org/package/threadsrel =nofollow>线程,你可以用同样的方法。
{ - #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)
实例MonadRunForkBase [] []其中runForkBase =返回
实例MonadRunForkBase IO IO其中runForkBase =返回
实例MonadRunForkBase STM STM其中runForkBase =返回
实例MonadRunForkBase可能Maybe其中runForkBase =返回
实例MonadRunForkBase Identity身份其中runForkBase =返回
对于变形金刚来说,一步一步建立功能通常更容易。这是一类可以在紧接着的monad中运行fork的转换器。
import Control.Monad.Trans.Class
class(MonadTrans t)=> MonadTransRunFork t其中
runFork :: Monad m => t m a - > tm(ma)
我们可以提供一个默认实现, p>
runForkBaseDefault ::(Monad(tm),MonadTransRunFork t,MonadRunForkBase bm)=>
t m a - > t m(b a)
runForkBaseDefault =(>> = lift。runForkBase)。 runFork
这让我们完成了一个 MonadRunForkBase
实例为 StateT
分两步执行。首先,我们将使用上面的 runFork
来创建 MonadTransRunFork
实例
import Control.Monad
导入合格的Control.Monad.Trans.State.Lazy作为状态
实例MonadTransRunFork(State.StateT s)其中
runFork x = State.get>> = return。 liftM fst。 State.runStateT x
然后我们将使用默认值来提供 MonadRunForkBase
instance。
{ - #LANGUAGE FlexibleInstances# - }
{ - #LANGUAGE UndecidableInstances # - }
实例(MonadRunForkBase bm)=> MonadRunForkBase b(State.StateT sm)其中
runForkBase = runForkBaseDefault
我们可以做同样的事情用于 RWS
导入限定的Control.Monad.Trans.RWS。 Lazy as RWS
instance(Monoid w)=> MonadTransRunFork(RWS.RWST rws)其中
runFork x = do
r < - RWS.ask
s < - RWS.get
return $ do
(a, s',w')< - RWS.runRWST xrs
返回a
实例(MonadRunForkBase bm,Monoid w)=> MonadRunForkBase b(RWS.RWST rwsm)其中
runForkBase = runForkBaseDefault
MonadBaseControl
与我们在前两节中开发的 MonadRunForkBase
不同, MonadBaseControl
来自 monad-control 并没有假设未来的状态变化一般不会在两种期货之间共享。 MonadBaseContol
和 control
努力使用 restoreM恢复控制结构中的分支状态: :StM ma - > m a
。这对基地的 forkIO
没有任何问题;使用 forkIO
是 MonadBaseControl
文档中提供的示例。这对于 <由于多余的 m(Result a)
返回,code> forkIO 来自线程。
我们想要的 m(Result a)
实际上会返回为 IO(Result(StM ma))
。我们可以用 liftBase $>来取代
IO
,并用 m
取代它。 c $ c>,给我们留下 m(Result(StM ma))
。我们可以将 StM ma
转换为 ma
,它可以恢复状态,然后返回 a
with
restoreM
,但它被卡在 Result〜Either SomeException
中。 l
是一个函子,因此我们可以在其中的任何地方应用 restoreM
,将类型简化为 m(Result(ma))
。 l
也是 Traversable
,对于任何 Traversable
t
我们可以随时在 Monad
或 Applicative
中将它与 sequenceA :: t(fa) - > f(t a)
。在这种情况下,我们可以使用特殊目的 mapM
,它是 fmap
和 sequenceA
只有一个 Monad
约束。这会给 m(m(Result a))
,并且 m
s会被连接放在一起Monad或简单地使用>> =
。这会产生
{ - #LANGUAGE FlexibleContexts# - }
import Control.Concurrent
导入Control.Concurrent.Thread
导入限定的Control.Concurrent.Thread.Group作为TG
导入Control.Monad.Base
导入Control.Monad.Trans.Control
import Data.Functor
import Data.Traversable
import Prelude隐藏(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 :: MonadBaseControl bm => m(m())
checkpoint = liftBaseWith(\runInBase - > runInBase(return()))
>> = return。 restoreM
一个完整的例子将显示两个线程状态的变化。无论在其他线程中修改状态的努力如何,两个线程都从 fork
发生时的状态。当我们在主线程中等待结果时,主线程中的状态被设置为来自分叉线程的状态。我们可以通过运行由 checkpoint
创建的动作来获取主线程的状态。
import Control.Monad.State hiding(mapM)
例如::(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)
得到>> = put。 (In Main:++)
还原< - 检查点
结果< - getResult
(liftBase。print)结果
get>> = liftBase。 putStrLn
还原
get>> = liftBase。 putStrLn
$ b $ main = do
runStateT示例Initial
return()
此输出
初始
右7
在叉:初始
Main:初始
In threads package in module Control.Concurrent.Thread.Group
there is a function forkIO
:
forkIO :: ThreadGroup -> IO α -> IO (ThreadId, IO (Result α))
I'd like to lift it using MonadBaseControl
from monad-control. Here is my attempt:
fork :: (MonadBase IO m) => TG.ThreadGroup -> m α -> m (ThreadId, m (Result α))
fork tg action = control (\runInBase -> TG.forkIO tg (runInBase action))
and here is the error messsage:
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))'
What to change to make the types match?
The main problem is the IO a
argument to forkIO
. To fork an m a
action in IO
we'd need a way to run an m a
to an IO a
. To do this, we could try to make the class of monads that have a runBase :: MonadBase b m => m a -> b a
method, but very few interesting transformers can provide that. If we consider for example the StateT
transformer, it could figure out how to run something in the base monad with runStateT
if it's first given an opportunity to observe its own state.
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
This suggests the type runForkBase :: MonadBase b m => m a -> m (b a)
, which we will settle on for the following type class.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
import Control.Monad.Base
class (MonadBase b m) => MonadRunForkBase b m | m -> b where
runForkBase :: m a -> m (b a)
I added the word Fork
to the name to emphasize that the future state changes will not in general be shared between the two futures. For this reason, the few interesting transformers like WriterT
that could have provided a runBase
only provide an uninteresting runBase
; they produce side effects that will never be observable.
We can write something like fork
for anything with the limited form of lowering provided by a MonadRunForkBase IO m
instance. I'm going to lift
the normal forkIO
from base rather than the one from threads, which you can do the same way.
{-# LANGUAGE FlexibleContexts #-}
import Control.Concurrent
forkInIO :: (MonadRunForkBase IO m) => m () -> m ThreadId
forkInIO action = runForkBase action >>= liftBase . forkIO
Instances
This raises the question, "What transformers can we provide MonadRunForkBase
instances for"? Straight off the bat, we can trivially provide them for any of the base monads that have MonadBase
instances
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
For transformers, it's usually easier to build up functionality like this step-by-step. Here's the class of transformers that can run a fork in the immediately underlying monad.
import Control.Monad.Trans.Class
class (MonadTrans t) => MonadTransRunFork t where
runFork :: Monad m => t m a -> t m (m a)
We can provide a default implementation for running all the way down in the base
runForkBaseDefault :: (Monad (t m), MonadTransRunFork t, MonadRunForkBase b m) =>
t m a -> t m (b a)
runForkBaseDefault = (>>= lift . runForkBase) . runFork
This lets us complete out a MonadRunForkBase
instance for StateT
in two steps. First, we'll use our runFork
from above to make a MonadTransRunFork
instance
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
Then we'll use the default to provide a MonadRunForkBase
instance.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
instance (MonadRunForkBase b m) => MonadRunForkBase b (State.StateT s m) where
runForkBase = runForkBaseDefault
We can do the same thing for 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
MonadBaseControl
Unlike MonadRunForkBase
which we developed in the previous two sections, the MonadBaseControl
from monad-control doesn't have baked in the assumption "future state changes will not in general be shared between the two futures". MonadBaseContol
and control
make an effort to restore the state from branching in control structures with restoreM :: StM m a -> m a
. This doesn't present a problem for the forkIO
from base; using forkIO
is an example provided in the MonadBaseControl
documentation. This will be a slight problem for the forkIO
from threads because of the extra m (Result a)
returned.
The m (Result a)
we want will actually be returned as an IO (Result (StM m a))
. We can get rid of the IO
and replace it with an m
with liftBase
, leaving us with m (Result (StM m a))
. We could convert an StM m a
into an m a
that restores state and then returns a
with restoreM
, but it is stuck inside a Result ~ Either SomeException
. Either l
is a functor, so we can apply restoreM
everywhere inside it, simplifying the type to m (Result (m a))
. Either l
is also Traversable
, and for any Traversable
t
we can always swap it inside a Monad
or Applicative
with sequenceA :: t (f a) -> f (t a)
. In this case, we can use the special purpose mapM
which is a combination of fmap
and sequenceA
with only a Monad
constraint. This would give m (m (Result a))
, and the m
s would be flattened together by a join in the Monad or simply using >>=
. This gives rise to
{-# 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)
When we run the m (Result a)
in the original thread, it will copy the state from the forked thread to the original thread, which may be useful. If you want to restore the state of the main thread after reading the Result
you'll need to capture it first. checkpoint
will capture the entire state and return an action to restore it.
checkpoint :: MonadBaseControl b m => m (m ())
checkpoint = liftBaseWith (\runInBase -> runInBase (return ()))
>>= return . restoreM
A complete example will show what happens to the state from two threads. Both threads get the state from when the fork
happened regardless of efforts to modify the state in the other thread. When we wait for the result in the main thread, the state in the main thread is set to the state from the forked thread. We can get the main thread's state back by running the action created by 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 ()
This outputs
Initial
Right 7
In Fork:Initial
In Main:Initial
这篇关于MonadBaseControl:如何解除ThreadGroup的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!