alms-0.6.7: src/Util/Undo.hs
{-# LANGUAGE ConstraintKinds, TypeFamilies #-}
-- | An extension of the IO monad with an undo facility
module Util.Undo (
-- * The 'UndoT' monad transformer and 'UndoIO' monad
UndoT(..), UndoIO,
-- ** Running
runUndoT, runUndoT',
runUndoIO, runUndoIO',
-- ** Operation
addUndo
) where
import Prelude
import Util.MonadRef
import Control.Applicative
import Control.Monad.Error
import Data.IORef
-- | A layer on top of the IO monad with an undo facility.
type UndoIO a = UndoT IORef IO a
newtype UndoT r m a
= UndoT {
unUndoT ∷ r [m ()] → m a
}
instance Applicative m => Applicative (UndoT r m) where
pure = UndoT . const . pure
UndoT f <*> UndoT a = UndoT (liftA2 (<*>) f a)
instance Functor m => Functor (UndoT r m) where
fmap f = UndoT . (fmap f .) . unUndoT
instance Monad m => Monad (UndoT r m) where
return = lift . return
m >>= k = UndoT $ \undo → do
a ← unUndoT m undo
unUndoT (k a) undo
instance MonadTrans (UndoT r) where
lift = UndoT . const
instance MonadIO m => MonadIO (UndoT r m) where
liftIO = lift . liftIO
-- | Run an 'UndoT' computation, running the undo list actions
-- if it raises an exception.
runUndoT ∷ (MonadError e m, MonadRef r m) => UndoT r m a → m a
runUndoT action = do
undo ← newRef []
unUndoT action undo `catchError` \e → do
sequence_ =<< readRef undo
throwError e
-- | Run an 'UndoT' computation, without checking for an escaping
-- exception.
runUndoT' ∷ MonadRef r m => UndoT r m a → m a
runUndoT' action = unUndoT action =<< newRef []
-- | Run an 'UndoIO' computation, running the undo list actions
-- if it raises an exception.
runUndoIO ∷ UndoIO a → IO a
runUndoIO = runUndoT
-- | Run an 'UndoT' computation, without checking for an escaping
-- exception.
runUndoIO' ∷ UndoIO a → IO a
runUndoIO' = runUndoT'
---
--- OPERATIONS
---
-- | Add an action to the front of the undo list
addUndo ∷ MonadRef r m => m () → UndoT r m ()
addUndo action = UndoT (modifyRef (action :))
instance MonadRef r m => MonadRef r (UndoT r m) where
newRef = lift . newRef
readRef = lift . readRef
writeRef r a = modifyRef (const a) r
modifyRef f r = UndoT $ \undo → do
old ← readRef r
writeRef r (f old)
modifyRef (writeRef r old :) undo
instance (MonadError e m, MonadRef r m) => MonadError e (UndoT r m) where
throwError = lift . throwError
catchError action handler = UndoT $ \undo → do
undo' ← newRef []
unUndoT action undo' `catchError` \exn → do
sequence_ =<< readRef undo'
unUndoT (handler exn) undo