ForkIO与stateT

时间:2013-08-14 14:02:27

标签: haskell

我们如何让用户传递一个eventHandler,它使用stateMonad但是在一个单独的线程中调用?例如,在以下示例中,应该如何调用forkIO以便eventHandler可以调用操作?我是Haskell的新手,请纠正我,如果这是一个错误的api暴露给用户?

data MyTypeResult a = MyTypeValue a
data MyTypeState = MyTypeState {_counter :: Int}

newtype MyType a = MyType {
      unMyType :: StateT MyTypeState IO (MyTypeResult a)
}

instance Monad MyType where
    (>>=) = myTypeBind
    return = myTypeReturn
    fail = myTypeFail

myTypeBind = undefined
myTypeReturn = undefined
myTypeFail = undefined

type Event = String
type Handler =  Event -> MyType ()

doSomethingAwesome :: MyType Event
doSomethingAwesome = undefined

operate :: String -> MyType ()
operate = undefined

start :: Handler -> MyType ()
start h = do
  event <- doSomethingAwesome
  --forkIO $ h event -- The line that is troubling
  return ()

testHandler :: Event -> MyType()
testHandler _ = operate "abcd"

myMain = start testHandler

3 个答案:

答案 0 :(得分:5)

你不能在共享相同状态的多个线程中运行State计算,因为在幕后,State monad只不过是一个函数调用链,它将状态值传递给链中的下一个功能。

对于多线程代码,您可以将StateT s IO替换为ReaderT (IORef s) IO并使用

forkIO $ runReaderT (h event) stateVar

分叉新线程(其中stateVar是包含共享状态的IORef。)

ReaderT堆栈中,您使用

读取当前的共享状态
stateVar <- ask
s <- lift $ readIORef stateVar

并使用

进行更新
stateVar <- ask
lift $ atomicModifyIORef stateVar f

其中f是一个纯函数,它接受当前状态并返回修改后的状态和辅助结果。

如果您需要更多花哨的东西(例如使用monadic函数修改状态),则应使用MVarTVar代替IORef

答案 1 :(得分:2)

虽然确实不能在多个线程中运行多个StateT s IO操作,所有线程都共享相同的s状态,但您可以在一个线程中运行StateT s IO,其状态处于隔离状态其他人使用monad-controllifted-base

我最近偶然发现了这些包装,这真是太神奇了。这是一个简单的例子

{-# LANGUAGE FlexibleContexts #-}

import Control.Concurrent.Lifted
import Control.Monad.Base
import Control.Monad.Trans.Control
import Control.Monad.State

t :: IO Int -> StateT Int IO ()
t io = replicateM_ 10 $ do
    x <- get
    y <- liftIO io
    liftIO $ print x
    put $ x + y

async :: MonadBaseControl IO m => (IO a -> m ()) -> m (a -> IO ())
async thread = do
    mvar <- liftBase newEmptyMVar
    fork $ thread (takeMVar mvar)
    return $ liftBase . putMVar mvar

main :: IO ()
main = evalStateT (async t) 0 >>= forM_ [1..10]

基本上,只要你的monad是IO之上的变换器堆栈,你就可以在你的变换器堆栈中“分叉”,处理异常或任何其他IO特定操作。

答案 2 :(得分:0)

forkIO将无法在MyType内部工作,直到您通过为MyType实施MonadIO类来使用liftIO,或者您可以将MyType设置为类型同义词并执行:

data MyTypeResult a = MyTypeValue a
data MyTypeState = MyTypeState {_counter :: Int}

type MyType a = StateT MyTypeState IO (MyTypeResult a)

type Event = String
type Handler =  Event -> MyType ()

doSomethingAwesome :: MyType Event
doSomethingAwesome = undefined

operate :: String -> MyType ()
operate = undefined

start :: Handler -> MyType ()
start h = do
    MyTypeValue event <- doSomethingAwesome
    st <- get
    liftIO $ forkIO $ runStateT (h event) st >> (return ())
    return $ MyTypeValue ()

testHandler :: Event -> MyType()
testHandler _ = operate "abcd"

myMain = start testHandler