首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >StateT与ReaderT IORef的异常处理

StateT与ReaderT IORef的异常处理
EN

Stack Overflow用户
提问于 2013-07-15 09:51:50
回答 2查看 674关注 0票数 3

通过持有IORef来通过异常维护状态似乎比尝试使用state Monad容易得多。下面我们有两个可供选择的State Monad。一个使用StateT,另一个使用ReaderT IORefReaderT IORef可以很容易地在最后一个已知状态上运行最终处理程序。

代码语言:javascript
复制
{-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
import Control.Monad.State (MonadState, execStateT, modify, StateT)
import Control.Applicative (Applicative)
import Control.Monad (void)
import Control.Monad.IO.Class ( MonadIO, liftIO )
import Data.IORef
import Control.Exception.Base
import Control.Monad.Reader (MonadReader, runReaderT, ask, ReaderT)

type StateRef = IORef Int
newtype ReadIORef a = ReadIORef { unStIORef :: ReaderT StateRef IO a } deriving (Functor, Applicative, Monad, MonadIO, MonadReader StateRef)
newtype St a        = StM       { unSt      :: StateT Int IO a } deriving (Functor, Applicative, Monad, MonadIO, MonadState Int)

eval :: St a -> Int -> IO Int
eval = execStateT . unSt

evalIORef :: ReadIORef a -> StateRef -> IO a
evalIORef = runReaderT . unStIORef

add1 :: St ()
add1 = modify (+ 1)

add1Error :: St ()
add1Error = do
  modify (+ 1)
  error "state modified"

add1IORef :: ReadIORef Int
add1IORef = do
  ioref <- ask
  liftIO $ do
    modifyIORef' ioref (+ 1)
    readIORef ioref

add1IORefError :: ReadIORef Int
add1IORefError = do
  ioref <- ask
  liftIO $ do
    modifyIORef' ioref (+ 1)
    void $ error "IORef modified"
    readIORef ioref

ignore :: IO a -> IO a
ignore action = catch action (\(_::SomeException) -> return $ error "ignoring exception")

main :: IO ()
main = do
  st <- newIORef 1
  resIO <- evalIORef add1IORef st >> evalIORef add1IORef st
  print resIO -- 3

  resSt <- eval add1 1 >>= eval add1
  print resSt -- 3

  stFinal <- newIORef 1
  void $ ignore $ finally (evalIORef add1IORefError stFinal) (evalIORef add1IORef stFinal)
  print =<< readIORef st -- 3

  -- how can the final handler function use the last state of the original?
  void $ ignore $ finally (eval add1Error 1) (eval add1 1)
  print "?"

那么,在main函数的末尾,我如何运行一个最终的处理程序来访问state Monad的最后一个现有状态,即使抛出了一个异常?或者ReaderT IORef是最优的,还是有更好的替代方案?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-07-15 11:08:01

有一种方法,但让我首先从ErrorTStateT的角度解释从错误中恢复状态,因为我发现它很好地说明了一般情况。

让我们首先想象一下ErrorT位于StateT外部的情况。换句话说:

代码语言:javascript
复制
m1 :: ErrorT e (StateT s m) r

如果同时展开ErrorTStateT新类型,则会得到:

代码语言:javascript
复制
runErrorT m1
    :: StateT s m (Either e r)

runStateT (runErrorT m1)
    :: s -> m (Either e r, s)

未包装类型表示,即使收到错误,我们也会恢复最终状态。所以请记住,StateT外部的ErrorT意味着我们可以在保留当前状态的同时从错误中恢复。

现在,让我们交换一下顺序:

代码语言:javascript
复制
m2  :: StateT s (ErrorT e m r)

runStateT m2
    :: s -> ErrorT e m (r, s)

runErrorT . runStateT m2
    :: s -> m (Either e (r, s))

这种类型讲述了一个不同的故事:如果我们的计算成功,我们只恢复结束状态。所以请记住,StateT内部的ErrorT意味着我们无法恢复状态。

对于熟悉mtl的人来说,这可能很奇怪,它为StateT提供了以下MonadError实例

代码语言:javascript
复制
instance (MonadError e m) => MonadError e (StateT s m) where ...

在我刚才说了这些之后,StateT是如何优雅地从错误中恢复的呢?好吧,事实证明it 不是。如果您编写以下代码:

代码语言:javascript
复制
(m :: StateT s (ErrorT e m) r) `catchError` f

..。如果m使用throwErrorf将从m的初始状态开始,而不是抛出错误时m所处的状态。

好了,现在来回答你的具体问题。默认情况下,可以认为IO有一个内置的ErrorT层。这意味着如果你不能摆脱这个ErrorT层,那么它将始终在你的StateT中,当它抛出错误时,你将无法恢复当前的状态。

类似地,您可以认为IO在缺省情况下具有一个位于ErrorT层之下的内置StateT层。从概念上讲,这一层包含IORef,并且因为它位于ErrorT层的“内部”,所以它总是在错误中存活下来并保留IORef值。

这意味着您可以使用IO monad之上的StateT层并使其在异常中幸存下来的唯一方法就是去掉IOErrorT层。只有一种方法可以做到这一点:

tryIO

  • Mask异步异常中,
  • 包装每个IO操作,并且只在tryIO语句中间取消它们的掩码。

我个人的建议是走IORef路线,因为有些人不喜欢在tryIO语句之外屏蔽异步异常,因为这样就不能中断纯计算。

票数 12
EN

Stack Overflow用户

发布于 2013-07-15 11:12:15

你是抛出这些异常,还是一个库?

因为如果是前者,为什么不使用EitherT转换器来进行异常处理呢?

您只需注意顺序:如果出现错误,StateT s (EitherT e IO) a不会让您看到最终状态,但EitherT e (StateT s IO) a会让您看到最终状态。

代码语言:javascript
复制
StateT s (EitherT e IO) a ~ IO (Either e (s -> (a,s)))
EitherT e (StateT s IO) a ~ IO (s -> (Either e a, s))

如果您正在使用抛出异常的库,并且希望维护状态,那么您将需要使用lift $ catch libraryCall exceptionHandler在state monad中捕获异常。

如果您试图在State monad之外捕获异常,就像您在这里所做的那样,那么这与StateT s (EitherT e IO) a同构,因为您正在使用IO中的错误功能来捕获。该状态在该级别不可用。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17645655

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档