For some file operation, I need to check if the file exists, if it has been modified, and only then perform some operation on it. My newbie Haskell code looks as follows (simplified):
someFileOp ::FileContents -> FilePath -> IO (FileOpResult)
someFileOp contents absFilePath = do
fileExists <- DIR.doesFileExist absFilePath
if fileExists
then do
isMod <- isModified contents absFilePath
if isMod
then return FileModified
else return $ doSomethingWithFile
else return FileNotFound
It does work. However, the nested if-expressions look wrong to me - not FP-like. What would be an idiomatic way to check several Boolean conditions in IO and then take some action depending on their result?
Ignoring Daniel's good point on races and why checking for files is often just not done, the more Haskell solution is usually a monad transformer. This is a typical case where an ExceptT transformer makes sense. I've also included a (mis)use of ContT in case you're curious and want to explore:
import System.Directory as DIR
import Control.Monad.Trans.Cont
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Except
isModified :: a -> b -> IO Bool
isModified _ _ = pure False
type FileOpResult = Either String String
someFileOp_cont :: String -> FilePath -> IO FileOpResult
someFileOp_cont contents absFilePath = evalContT $ callCC $ \exit -> do
fileExists <- liftIO $ DIR.doesFileExist absFilePath
unless fileExists (exit (Left "FileNotFound"))
isMod <- liftIO $ isModified contents absFilePath
when isMod (exit (Left "FileModified"))
return (Right "doSomethingWithFile")
someFileOp_except :: String -> FilePath -> IO FileOpResult
someFileOp_except contents absFilePath = runExceptT $ do
fileExists <- liftIO $ DIR.doesFileExist absFilePath
unless fileExists (throwE "FileNotFound")
isMod <- liftIO $ isModified contents absFilePath
when isMod (throwE "FileModified")
return "doSomethingWithFile"
I'd use whenM :: Monad m => m Bool -> m () -> m()
or ifM :: Monad m => m Bool -> m a -> m a -> m a
, available for example in extra
:
-- | Like 'when', but where the test can be monadic.
whenM :: Monad m => m Bool -> m () -> m ()
whenM mb mt = mb >>= \b ->
if b
then mt
else return ()
-- | Like @if@, but where the test can be monadic.
ifM :: Monad m => m Bool -> m a -> m a -> m a
ifM mb mt me = mb >>= \b ->
if b
then mt
else me
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With