Determine if a list of files exist in Haskell
I'm a newbie, and the monads get me totally confused. Given a list of filenames i'd like to know whether all the files exist.
Generally, i'd like to do:
import System.Directory
allFilesPresent files = foldr (&&) True (map doesFileExist files)
开发者_如何学CHowever i do not know what's the right way to do it, because there's IO Bool
instead of Bool
involved here.
A help with and explanation would be really nice, thanks!
You're right, your code doesn't work because map doesFileExist files
returns a list of IO Bool
s instead of Bool
. To fix this, you can use mapM
instead of map
, which will give you an IO [Bool]
. You can the unpack that using >>=
or <-
inside a do
-block and then use foldr (&&)
on the unpacked [Bool]
and return
that. The result will be an IO Bool
. Like this:
import System.Directory
allFilesPresent files = mapM doesFileExist files >>=
return . foldr (&&) True
Or using do 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
doesFileExist "foo.txt"
is an IO Bool
, which means its result depends on the state of the outside world.
You're on the right track with map doesFileExist files
-- this expression will return [IO Bool]
, or a list of world-dependent expressions. What's actually needed is an IO
expression containing a list of bools. You can get this using sequence
:
sequence :: Monad m => [m a] -> m [a]
or, since you're just using sequence/map, the mapM
helper function:
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f xs = sequence (map f xs)
Lets go back to your code. Here's a 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)
An alternative version uses more declarative syntax; this is probably what an experienced Haskell programmer would write:
allFilesPresent :: [String] -> IO Bool
allFilesPresent = fmap and . mapM doesFileExist
Note that if you use sequence
or mapM
, you are choosing not to short-circuit; even if one of the files turns out not to exist, you still check the rest of the files for existence. If you want to 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 by using monad-loops package:
import System.Directory
import Control.Monad.Loops
allFilesPresent :: [FilePath] -> IO Bool
allFilesPresent = allM doesFileExist
精彩评论