IORef
State モナドを使おうとするよりも、 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
最適ですか、それともより良い代替手段がありますか?