Home > Enterprise >  How to use Haskell's readFile function for multiple files
How to use Haskell's readFile function for multiple files

Time:02-14

I understand a little bit about IO. I understand that you can use readFile to get the content of a file. For example like this:

main = do
    let inputFilePath = "C:\\Haskell\\myawesomeprogram\\files\\a.txt"
    content <- readFile inputFilePath
    print content

Calling the program:

> runghc myawesomeprogram
"AAA"

Awesome, that works! Now I want to read the content from multiple files. I tried something like this:

files = ["C:\\Haskell\\myawesomeprogram\\files\\a.txt", "C:\\Haskell\\myawesomeprogram\\files\\b.txt","C:\\Haskell\\myawesomeprogram\\files\\c.txt"]


main :: IO ()
main = do
    filesContent <- readFiles files
    print filesContent

readFiles (x:xs) = do 
    content <- readFile x
    content : readFiles xs

This will give me the following error message:

myawesomeprogram.hs:6:21: error:
    * Couldn't match type `[]' with `IO'
      Expected type: IO String
        Actual type: [String]
    * In a stmt of a 'do' block: filesContent <- readFiles files
      In the expression:
        do filesContent <- readFiles files
           print filesContent
      In an equation for `main':
          main
            = do filesContent <- readFiles files
                 print filesContent
  |
6 |     filesContent <- readFiles files
  |                     ^^^^^^^^^^^^^^^

myawesomeprogram.hs:9:1: error:
    Couldn't match type `IO' with `[]'
    Expected type: [FilePath] -> [String]
      Actual type: [FilePath] -> IO String
  |
9 | readFiles (x:xs) = do
  | ^^^^^^^^^^^^^^^^^^^^^^^...

myawesomeprogram.hs:11:5: error:
    * Couldn't match type `[]' with `IO'
      Expected type: IO String
        Actual type: [String]
    * In a stmt of a 'do' block: content : readFiles xs
      In the expression:
        do content <- readFile x
           content : readFiles xs
      In an equation for `readFiles':
          readFiles (x : xs)
            = do content <- readFile x
                 content : readFiles xs
   |
11 |     content : readFiles xs
   |     ^^^^^^^^^^^^^^^^^^^^^^

I'm doing something wrong, however, I can't see a way to do it right. Can you do it the right way?

CodePudding user response:

readFiles xs is not a list, so you can't prepend an item to it. Instead, it's an action, which, when executed, would produce a list.

More specifically, the type of readFiles xs is IO [String] (IO is the type of executable actions), whereas a list would have type [String]. This is what the error message is telling you: cannot match type IO [String] with [String].

So to get the list, you have to execute the action, just like you're executing readFile

readFiles (x:xs) = do
    content <- readFile x
    theRest <- readFiles xs
    pure (content : theRest)

Also note that readFiles doesn't know what to do when its argument is an empty list. You should get a warning about it at compile time, and if you don't fix it, you'll get a crash at runtime.

To fix, just add an equation for the empty list case:

readFiles [] = pure []
readFiles (x:xs) = do
    content <- readFile x
    theRest <- readFiles xs
    pure (content : theRest)
  • Related