haskell 如何在IO异常处理程序中保留monad堆栈的状态?

lymnna71  于 2022-11-14  发布在  其他
关注(0)|答案(1)|浏览(102)

请考虑以下程序。

import Control.Monad.State
import Control.Monad.Catch

ex1 :: StateT Int IO ()
ex1 = do
    modify (+10)
    liftIO . ioError $ userError "something went wrong"

ex2 :: StateT Int IO ()
ex2 = do
    x <- get
    liftIO $ print x

ex3 :: StateT Int IO ()
ex3 = ex1 `onException` ex2

main :: IO ()
main = evalStateT ex3 0

当我们运行程序时,我们得到以下输出。

$ runhaskell Test.hs
0
Test.hs: user error (something went wrong)

然而,我期望输出如下。

$ runhaskell Test.hs
10
Test.hs: user error (something went wrong)

如何在异常处理程序ex2中保留ex1中的中间状态?

dpiehjr4

dpiehjr41#

请使用IORef(或MVarTVar或其他)来代替。

newtype IOStateT s m a = IOStateT { unIOStateT :: ReaderT (IORef s) m a }
    deriving (Functor, Applicative, Monad, MonadTrans, MonadIO)
    -- N.B. not MonadReader! you want that instance to pass through,
    -- unlike ReaderT's instance, so you have to write the instance
    -- by hand

runIOStateT :: IOStateT s m a -> IORef s -> m a
runIOStateT = runReaderT . unIOStateT -- or runIOStateT = coerce if you're feeling cheeky

instance MonadIO m => MonadState s (IOStateT s m) where
    state f = IOStateT $ do
        ref <- ask
        liftIO $ do
            s <- readIORef ref
            let (a, s') = f s
            writeIORef ref s'
            pure a

这感觉像一个模式,我已经看到了足够的时间,应该有一个黑客包,但我不知道一个。

相关问题