haskell-awk-1.2: src/Control/Monad/Trans/State/Persistent.hs
{-# LANGUAGE PackageImports, ScopedTypeVariables #-}
-- | In which the state of a State monad is persisted to disk.
module Control.Monad.Trans.State.Persistent where
import Control.Applicative
import Control.Monad
import Control.Monad.IO.Class
import "mtl" Control.Monad.Trans
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.State
import Data.Functor.Identity
import System.Directory
import System.IO
-- $setup
-- >>> import System.FilePath
-- >>> tmp <- getTemporaryDirectory
-- >>> let f = tmp </> "doctest.txt"
-- | Read and write the cache to a file. Not atomic.
--
-- >>> :{
-- do { exists <- doesFileExist f
-- ; when exists $ removeFile f
-- }
-- :}
--
-- >>> withPersistentState f 0 $ modify (+1) >> get
-- 1
-- >>> withPersistentState f 0 $ modify (+1) >> get
-- 2
--
-- >>> removeFile f
withPersistentState :: forall s a. (Read s, Show s, Eq s)
=> FilePath -> s -> State s a -> IO a
withPersistentState f default_s sx = do
withPersistentStateT f default_s sTx
where
sTx :: StateT s IO a
sTx = mapStateT (return . runIdentity) sx
-- | A monad-transformer version of `withPersistentState`.
--
-- >>> :{
-- do { exists <- doesFileExist f
-- ; when exists $ removeFile f
-- }
-- :}
--
-- >>> withPersistentStateT f 0 $ lift (putStrLn "hello") >> modify (+1) >> get
-- hello
-- 1
-- >>> withPersistentStateT f 0 $ lift (putStrLn "hello") >> modify (+1) >> get
-- hello
-- 2
--
--
-- If the contents of the file has been corrupted, revert to the default value.
--
-- >>> withPersistentStateT f "." $ lift (putStrLn "hello") >> modify (++".") >> get
-- hello
-- ".."
-- >>> withPersistentStateT f "." $ lift (putStrLn "hello") >> modify (++".") >> get
-- hello
-- "..."
--
--
-- >>> removeFile f
withPersistentStateT :: forall m s a. (Functor m, MonadIO m, MonadFail m, Read s, Show s, Eq s)
=> FilePath -> s -> StateT s m a -> m a
withPersistentStateT f default_s sx = do
Just s <- runMaybeT (get_s <|> get_default_s)
(x, s') <- runStateT sx s
when (s' /= s) $ do
liftIO $ writeFile f $ show s'
return x
where
get_s :: MaybeT m s
get_s = do
exists <- liftIO $ doesFileExist f
guard exists
-- close the file even if the parsing fails
Just s <- liftIO $ withFile f ReadMode $ \h -> do
file_contents <- hGetContents h
case reads file_contents of
[(s, "")] -> return (Just s)
_ -> return Nothing
return s
get_default_s :: MaybeT m s
get_default_s = return default_s
-- | Combine consecutive StateT transformers into a single StateT, so the state
-- can be persisted to a single file.
--
-- >>> let sx = modify (+10) :: StateT Int (State Int) ()
-- >>> let tx = lift $ modify (*2) :: StateT Int (State Int) ()
-- >>> execState (withCombinedState $ sx >> tx) (1, 2)
-- (11,4)
withCombinedState :: Monad m => StateT s (StateT t m) a -> StateT (s, t) m a
withCombinedState ssx = do
(s, t) <- get
((x, s'), t') <- lift $ runStateT (runStateT ssx s) t
put (s', t')
return x