Skip to content

Instantly share code, notes, and snippets.

@parsonsmatt
Created April 13, 2018 00:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save parsonsmatt/5cb35804adc70dd3445fc3de8c75b80f to your computer and use it in GitHub Desktop.
Save parsonsmatt/5cb35804adc70dd3445fc3de8c75b80f to your computer and use it in GitHub Desktop.
-- Why use `mtl` constraints?
-- well, suppose you've got some tests using `MonadState`, but then that was a
-- mistake, because of the way that Alternative's IO works:
-- you had state rollback! Oh no!
--
-- Fortunately, it is relatively easy to write a state instance that Just Works
-- with IO references, which preserves state across threads and exceptions.
-- And, with GeneralizedNewtypeDeriving, you get all the necessary instance
-- boilerplate for free.
--
-- The only boring instance I have to write is the MonadState one.
--
-- The only change I needed to make to the code was swapping `execStateT` for `execRefT`.
newtype RefT s m a
= RefT
{ unRefT :: ReaderT (IORef s) m a
} deriving
( Functor, Applicative, Monad, MonadIO, MonadTrans, Alternative, MonadCatch
, MonadThrow, MonadPlus
)
instance MonadIO m => MonadState s (RefT s m) where
get = RefT ask >>= readIORef
put x = RefT ask >>= (`writeIORef` x)
runRefT :: MonadIO m => RefT s m a -> s -> m (a, s)
runRefT act i = do
init <- newIORef i
a <- runReaderT (unRefT act) init
s <- readIORef init
pure (a, s)
execRefT :: MonadIO m => RefT s m a -> s -> m s
execRefT x = fmap snd . runRefT x
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment