开发者

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 Bools 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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜