如何在 IO 异常处理程序中保留 monad 堆栈的状态?
How to preserve the state of the monad stack in the IO exception handler?
考虑以下程序。
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
中的中间状态?
改用 IORef
(或 MVar
或 TVar
或其他)。
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
我觉得这种模式我已经见过很多次了,应该有一个 Hackage 包,但我不知道有一个。
考虑以下程序。
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
中的中间状态?
改用 IORef
(或 MVar
或 TVar
或其他)。
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
我觉得这种模式我已经见过很多次了,应该有一个 Hackage 包,但我不知道有一个。