14

I'm new to Haskell and FP so this question may seem silly.

I have a line of code in my main function

let y = map readFile directoryContents

where directoryContents is of type [FilePath]. This in turn (I think) makes y type [IO String] , so a list of strings - each string containing the contents of each file in directoryContents.

I have a functions written in another module that work on [String] and String but I'm unclear as how to call/use them now because y is of type [IO String]. Any pointers?


EDIT:

It was suggested to me that I want to use mapM instead of map, so:

let y = mapM readFile directoryContents , and y is now type IO [String], what do I do from here?

chamini2
  • 2,820
  • 2
  • 24
  • 37
user3369427
  • 425
  • 6
  • 14
  • FYI- this is basically the "input" version of a similar question earlier today- http://stackoverflow.com/questions/22203811/iteratively-printing-every-integer-in-a-list. You might find the answers to that question interesting also. – jamshidh Mar 06 '14 at 02:56

1 Answers1

34

You're correct, the type is y :: [IO String].

Well, there are essentially main two parts here:

How to turn [IO String] into IO [String]

[IO String] is a list of of IO actions and what we need is an IO action that carries a list of strings (that is, IO [String]). Luckily, the function sequence provides exactly what we need:

sequence :: Monad m => [m a] -> m [a]
y' = sequence y :: IO [String]

Now the mapM function can simplify this, and we can rewrite y' as:

y' = mapM readFile directoryContents

mapM does the sequence for us.

How to get at the [String]

Our type is now IO [String], so the question is now "How do we get the [String] out of the IO?" This is what the function >>= (bind) does:

(>>=) :: Monad m => m a -> (a -> m b) -> m b
  -- Specialized to IO, that type is:
(>>=) :: IO a -> (a -> IO b) -> IO b

We also have a function return :: Monad m => a -> m a which can put a value "into" IO.

So with these two functions, if we have some function f :: [String] -> SomeType, we can write:

ourResult = y' >>= (\theStringList -> return (f theStringList))  :: IO SomeType

Functions can be "chained" together with the >>= function. This can be a bit unreadable at times, so Haskell provides do notation to make things visually simpler:

ourResult = do
  theStringList <- y'
  return $ f theStringList

The compiler internally turns this into y' >>= (\theStringList -> f theStringList), which is the same as the y' >>= f that we had before.

Putting it all together

We probably don't actually want y' floating around, so we can eliminate that and arrive at:

ourResult = do
  theStringList <- mapM readFile directoryContents
  return $ f theStringList

Even more simplification

It turns out, this doesn't actually need the full power of >>=. In fact, all we need is fmap! This is because the function f only has one argument "inside" of IO and we aren't using any other previous IO result: we're making a result then immediately using it.

Using the law

fmap f xs  ==  xs >>= return . f

we can rewrite the >>= code to use fmap like this:

ourResult = fmap f (mapM readFile directoryContents)

If we want to be even more terse, there is an infix synonym for fmap called <$>:

ourResult = f <$> mapM readFile directoryContents
David Young
  • 10,713
  • 2
  • 33
  • 47
  • thanks for the explanation! just one last thing a little unrelated if you don't mind: directoryContents contains the files ".." and "." and I think I'm getting and openFile error when readFile tries to open ".." and ".". Any idea how to fix this? – user3369427 Mar 06 '14 at 03:15
  • 2
    You could use `drop 2`. `drop 2 [".", "..", "a", "b", "c"] == ["a", "b", "c"]` – David Young Mar 06 '14 at 03:22
  • A good and comprehensive explanation, but I'd changed `fmap` to `liftM` to not to confuse up things, because `fmap` is defined for functors, whereas `>>=` and `liftM` are for monads. Heck, we still don't have `Monad` as a subclass of `Functor` :( – Yuuri Mar 06 '14 at 08:24
  • 1
    @user3369427 I'd also propose to filter a list of `FilePath`s before reading with `filterM doesFileExist`, because if you have some subfolders in your directory, the current code will b0rk as well. – Yuuri Mar 06 '14 at 08:28