Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List of IO Strings

Tags:

io

haskell

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?

like image 258
user3369427 Avatar asked Mar 06 '14 02:03

user3369427


1 Answers

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
like image 81
David Young Avatar answered Nov 08 '22 09:11

David Young