我们如何让用户传递一个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
答案 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函数修改状态),则应使用MVar
或TVar
代替IORef
。
答案 1 :(得分:2)
虽然确实不能在多个线程中运行多个StateT s IO
操作,所有线程都共享相同的s
状态,但您可以在一个线程中运行StateT s IO
,其状态处于隔离状态其他人使用monad-control
和lifted-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