3

IORefState モナドを使おうとするよりも、 an にしがみついて例外を通して状態を維持する方がはるかに簡単に思えます。以下に、2 つの代替状態モナドがあります。1 つは を使用StateTし、もう1 つは を使用しReaderT IORefます。はReaderT IORef、最後の既知の状態で最終ハンドラーを簡単に実行できます。

{-# 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最適ですか、それともより良い代替手段がありますか?

4

2 に答える 2

1

これらの例外をスローしていますか、それともライブラリですか?

前者の場合、EitherT トランスフォーマーを使用して例外処理を行ってみませんか?

順序に注意する必要があります。StateT s (EitherT e IO) aエラーが発生した場合、最終状態は表示されませんが、表示されEitherT e (StateT s IO) aます。

StateT s (EitherT e IO) a ~ IO (Either e (s -> (a,s)))
EitherT e (StateT s IO) a ~ IO (s -> (Either e a, s))

例外をスローするライブラリを使用していて、状態を維持したい場合は、 を使用して State モナド内で例外をキャプチャする必要がありますlift $ catch libraryCall exceptionHandler

ここで行っているように State モナドの外で例外をキャッチしようとすると、キャッチを行うためStateT s (EitherT e IO) aに内部のエラー機能を使用しているため、それは に同型ですIO。状態はそのレベルでは利用できません。

于 2013-07-15T03:12:15.287 に答える