alms-0.4.9: src/ErrorST.hs
-- | A semi-transactional version of the ST monad
{-# LANGUAGE
DeriveDataTypeable,
FlexibleInstances,
GeneralizedNewtypeDeriving,
MultiParamTypeClasses,
RankNTypes #-}
module ErrorST (
-- * The 'ST' monad with errors
ST,
-- ** Operations
runST, transaction, liftST,
catchError, throwError,
-- * 'STRef's
STRef,
-- ** Operations
newSTRef, newTransSTRef, readSTRef, writeSTRef, modifySTRef,
unsafeIOToST
) where
import Control.Applicative
import Control.Monad.Error
import Control.Monad.State
import qualified Control.Monad.ST as Super
import Data.Data
import qualified Data.STRef as S
-- | Like the 'ST' monad, but with errors and transactions. Each STRef
-- is declared to be transaction alor not. Transaction STRefs lose
-- any changes made between an exception handler and an exception
-- being thrown.
newtype ST s e a = ST { unST :: Rep s e a }
deriving (Functor, Monad, Typeable)
type Rep s e a = ErrorT e (StateT (Super.ST s ()) (Super.ST s)) a
instance Error e => Applicative (ST s e) where
pure = return
(<*>) = ap
instance Error e => MonadError e (ST s e) where
throwError = ST . throwError
catchError body handler = ST $ do
oldUndo <- get
put (return ())
do res <- unST body
modify (>> oldUndo)
return res
`catchError` \e -> do
newUndo <- get
put oldUndo
liftST_ newUndo
unST (handler e)
runST :: Error e => (forall s. ST s e a) -> Either e a
runST block =
Super.runST (evalStateT (runErrorT (unST (transaction block))) (return ()))
-- | Run something directly in the underlying ST monad
liftST :: Error e => Super.ST s a -> ST s e a
liftST = ST . liftST_
transaction :: Error e => ST s e a -> ST s e a
transaction block = block `catchError` throwError
data STRef s a
= NonTr {
getRef :: !(S.STRef s a)
}
| Trans {
getRef :: !(S.STRef s a)
}
deriving Typeable
-- | Create a new 'STRef' whose changes survive failed transactions
newSTRef :: Error e => a -> ST s e (STRef s a)
newSTRef = liftM NonTr . ST . liftST_ . S.newSTRef
-- | Create a new 'STRef' whose changes are reverted by failed transactions
newTransSTRef :: Error e => a -> ST s e (STRef s a)
newTransSTRef = liftM Trans . ST . liftST_ . S.newSTRef
readSTRef :: Error e => STRef s a -> ST s e a
readSTRef = ST . liftST_ . S.readSTRef . getRef
writeSTRef :: Error e => STRef s a -> a -> ST s e ()
writeSTRef (NonTr r) a = ST . liftST_ . S.writeSTRef r $ a
writeSTRef (Trans r) a = ST $ do
old <- liftST_ (S.readSTRef r)
addUndo_ (S.writeSTRef r old)
liftST_ (S.writeSTRef r a)
modifySTRef :: Error e => STRef s a -> (a -> a) -> ST s e ()
modifySTRef (NonTr r) f = ST . liftST_ . S.modifySTRef r $ f
modifySTRef (Trans r) f = ST $ do
old <- liftST_ (S.readSTRef r)
addUndo_ (S.writeSTRef r old)
liftST_ (S.writeSTRef r (f old))
unsafeIOToST :: Error e => IO a -> ST s e a
unsafeIOToST = ST . liftST_ . Super.unsafeIOToST
-- helpers
addUndo_ :: Error e => Super.ST s () -> Rep s e ()
addUndo_ = modify . (>>)
liftST_ :: Error e => Super.ST s a -> Rep s e a
liftST_ = lift . lift
{-
test :: IO ()
test = either fail id . runST $ do
a <- newSTRef "a0"
b <- newTransSTRef "b0"
c <- newSTRef "c0"
d <- newTransSTRef "d0"
e <- newSTRef "e0"
f <- newTransSTRef "f0"
do
writeSTRef a "a1"
writeSTRef b "b1"
writeSTRef d "d1"
transaction $ do
writeSTRef c "c2"
writeSTRef d "d2"
throwError "ERROR!"
writeSTRef a "a3"
writeSTRef b "b3"
`catchError` \_ -> do
writeSTRef e "e4"
writeSTRef f "f4"
ra <- readSTRef a
rb <- readSTRef b
rc <- readSTRef c
rd <- readSTRef d
re <- readSTRef e
rf <- readSTRef f
return $
print [(ra, "a1"), (rb, "b0"),
(rc, "c2"), (rd, "d0"),
(re, "e4"), (rf, "f4")]
-}