Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the idiomatic Haskell-way to act on predicates in IO?

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?

like image 387
Ulrich Schuster Avatar asked Dec 08 '22 10:12

Ulrich Schuster


2 Answers

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"
like image 157
Thomas M. DuBuisson Avatar answered Feb 16 '23 02:02

Thomas M. DuBuisson


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
like image 25
Sebastian Graf Avatar answered Feb 16 '23 01:02

Sebastian Graf