Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to preserve the state of the monad stack in the IO exception handler?

Consider the following program.

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

When we run the program we get the following output.

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

However, I expected the output to be as follows.

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

How do I preserve the intermediate state in ex1 in the exception handler ex2?

like image 648
Aadit M Shah Avatar asked Mar 18 '21 03:03

Aadit M Shah


People also ask

What is the Monad instance declaration for state?

The Monad instance declaration for State looks something like this: instance Monad (State s) where return x = state (\st -> (x, st)) act >>= k = state $ \st -> let (x, st') = runState act st in runState (k x) st' Notice that State s is not a type but a type constructor: it needs one more type variable to become a type.

How do you use state monad?

State monad code looks as if the state were a global mutable variable. You access it using get with no arguments, and you modify it by calling put that returns no value. So what have we gained in comparison to C? We might not see the hidden effects, but the compiler does. It desugars every do block and type-checks it.

What is monad bind in Haskell?

Monadic bind makes sure that the state is threaded from function to function. It's never shared. If you make your Haskell code concurrent, there will be no data races. Ex 1. Define the reader monad. It's supposed to model computations that have access to some read-only environment.

How to solve IOException in Java?

How to solve java.io.IOException IOException is a Java exception that occurs when an IO operation fails. Develop can explicitly handle the exception in a try-catch-finally block and print out the root cause of the failure. The developer can take the correct actions to solve this situation by having additional code in the catch and finally blocks.


Video Answer


1 Answers

Use an IORef (or MVar or TVar or whatever) instead.

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

This feels like a pattern I've seen enough times that there ought to be a Hackage package for it, but I don't know of one.

like image 122
Daniel Wagner Avatar answered Oct 21 '22 20:10

Daniel Wagner