3

我们如何让用户传递一个 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
4

3 回答 3

6

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

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

forkIO $ runReaderT (h event) stateVar

派生新线程(其中stateVarIORef包含共享状态的)。

ReaderT堆栈中,您读取当前共享状态

stateVar <- ask
s <- lift $ readIORef stateVar

并更新它

stateVar <- ask
lift $ atomicModifyIORef stateVar f

wheref是一个纯函数,它接受当前状态并返回修改后的状态加上一个辅助结果。

如果您需要更多花哨的东西(例如使用单子函数修改状态),那么您应该使用MVarorTVar代替IORef.

于 2013-08-14T15:25:59.510 回答
2

虽然确实不能StateT s IO在多个线程中运行多个操作,所有操作都共享相同的s状态,但您可以在一个线程中运行,其状态与使用andStateT 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特定操作。

于 2013-08-14T17:41:44.613 回答
0

forkIOMyType在您liftIO通过为 MyType 实现 MonadIO 类使用之前不会从内部工作,或者您可以使 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
于 2013-08-14T16:31:55.597 回答