tags:

views:

95

answers:

3

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!

+6  A: 

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
sepp2k
TomMD
Or if you're comfortable installing Haskell libraries, the monad-loops library has a function `allM :: Monad m => (a -> m Bool) -> [a] -> m Bool` in the `Control.Monad.Loops` module. With that function, `allFilesPresent = allM doesFileExist`
mokus
+4  A: 

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
John Millikin
I think you mean `allFilesPresent = fmap and . mapM doesFileExist`
HaskellElephant
It's not accurate to say that an `IO T` is a `T` that depends on the world. It's a *recipe* for getting a `T`, which could be executed more than once and produce a different result each time. Recipes and cakes are totally different kinds of thing.
keegan
@keegan: agreed, and to elaborate on that metaphor, the final product of _executing_ a cake recipe is the thing that I would call a cake that depends on the prior state of the world. From any given recipe (IO Cake) many such cakes can be created.
mokus
+3  A: 

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
Tsuyoshi Ito