MonadBaseControl:如何解除ThreadGroup [英] MonadBaseControl: how to lift ThreadGroup

查看:92
本文介绍了MonadBaseControl:如何解除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 ms 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆