Determine if a file list exists in Haskell

I'm new, and monads completely embarrass me. Given a list of file names, I would like to know if all files exist.

Generally, I would like to do:

import System.Directory allFilesPresent files = foldr (&&) True (map doesFileExist files) 

However, I do not know how to do it right, because IO Bool is used here instead of Bool .

Help and explanation will be very enjoyable, thanks!

+4
source share
3 answers

You are right, your code does not work, because map doesFileExist files returns an IO Bool list instead of a Bool . To fix this, you can use mapM instead of map , which will give you IO [Bool] . You can unpack using >>= or <- inside the do block, and then use foldr (&&) for the unpacked [Bool] and return , which. The result will be IO Bool . Like this:

 import System.Directory allFilesPresent files = mapM doesFileExist files >>= return . foldr (&&) True 

Or using the notation:

 import System.Directory allFilesPresent files = do bools <- mapM doesFileExist files return $ foldr (&&) True bools 

Or using liftM from Control.Monad :

 allFilesPresent files = liftM (foldr (&&) True) $ mapM doesFileExist files 

Or using <$> from Control.Applicative :

 allFilesPresent files = foldr (&&) True <$> mapM doesFileExist files 
+10
source

doesFileExist "foo.txt" is an IO Bool , which means that its result depends on the state of the outside world.

You are on the right track with map doesFileExist files - this expression will return [IO Bool] or a list of world-dependent expressions. In fact, an IO expression containing a list of bools is needed. You can get this with sequence :

 sequence :: Monad m => [ma] -> m [a] 

or, since you just use the sequence / map, the mapM helper function:

 mapM :: Monad m => (a -> mb) -> [a] -> m [b] mapM f xs = sequence (map f xs) 

Let's get back to your code. Here is the version using mapM , with comments:

 import System.Directory -- When figuring out some unfamiliar libraries, I like to use type annotations -- on all top-level definitions; this will help you think through how the types -- match up, and catch errors faster. allFilesPresent :: [String] -> IO Bool -- Because allFilesPresent returns a computation, we can use do-notation to write -- in a more imperative (vs declarative) style. This is sometimes easier for students -- new to Haskell to understand. allFilesPresent files = do -- Run 'doesFileExist' tests in sequence, storing the results in the 'filesPresent' -- variable. 'filesPresent' is of type [Bool] filesPresent <- mapM doesFileExist files -- The computation is complete; we can use the standard 'and' function to join the -- list of bools into a single value. return (and filesPresent) 

The alternate version uses a more declarative syntax; this is probably what the experienced Haskell programmer wrote:

 allFilesPresent :: [String] -> IO Bool allFilesPresent = fmap and . mapM doesFileExist 
+6
source

Note that if you use sequence or mapM , you choose not a closure; even if one of the files turns out to be non-existent, you still check the rest of the files for presence. If you want a short circuit, the following works:

 import System.Directory andM :: Monad m => [m Bool] -> m Bool andM [] = return True andM (m : ms) = do b <- m if b then andM ms else return False allFilesPresent :: [FilePath] -> IO Bool allFilesPresent files = andM $ map doesFileExist files 

Or, equivalently, using the monad-loops package :

 import System.Directory import Control.Monad.Loops allFilesPresent :: [FilePath] -> IO Bool allFilesPresent = allM doesFileExist 
+5
source

All Articles