Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine if a list of files exist in Haskell

Tags:

haskell

monads

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)

However 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!

like image 750
Drakosha Avatar asked Oct 20 '10 21:10

Drakosha


2 Answers

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
like image 172
sepp2k Avatar answered Nov 20 '22 05:11

sepp2k


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
like image 6
John Millikin Avatar answered Nov 20 '22 05:11

John Millikin