0

I have a text file with the following data:

3
7 4
2 4 6
8 5 9 3

I want to essentially extract this data and return it in the form:

["3", "7 4", "2 4 6", "8 5 9 3"]

I used readFile to get the entire file as a single string, and then used lines to parse the individual line based on the '\n' character. I tried doing this but the problem I'm getting is that readFile returns IO String, but I want String instead. Here's the code:

readLines :: FilePath -> [String]
readLines str = do
                  file <- readFile str
                  let list = lines (file :: String) 
                  list

How do I deal with this problem?

JFMR
  • 23,265
  • 4
  • 52
  • 76
  • 5
    You don't; this should be the first thing you learn about `IO` types. You don't get the data *out*; you build *new* IO actions by (e.g) binding the value to a function using `>>=`. – chepner Jan 28 '23 at 15:37
  • I understand that now. Can you elaborate a bit on the binding part? – Daniel McAllister Jan 28 '23 at 16:01
  • `readLines "foo.txt" >>= someFunction` would be an IO action that, when executed, executes the action provided by `readLines "foo.txt"`, takes the resulting list, and passes it to `someFunction`. (`someFunction` itself has to be a function that returns an `IO` action. *You* never actually get to see the list read from `foo.txt`, but `someFunction` does. – chepner Jan 28 '23 at 16:26

1 Answers1

4

The purpose of IO is to prevent you from extracting data from I/O operations. Instead, your function will also return an IO action that, when executed at runtime, will produce a list of values.

readLines :: FilePath -> IO [String]
readLines str = do
                   file <- readFile str
                   let list = lines (file :: String)
                   return list

or more simply, because IO (like any monad) is a functor,

readLines :: FilePath -> IO [String]
readLines str = lines <$> readFile str
chepner
  • 497,756
  • 71
  • 530
  • 681
  • That makes sense. My end goal is to take the output from readLines and read the individual strings inside and create a list of [Ints]. i.e., ["3", "7 4", "2 4 6", "8 5 9 3"] turns into [[3], [7, 4], [2, 4, 6], [8, 5, 9, 3]]. But the problem is that I can't perform list operations on IO [String] if that makes sense. – Daniel McAllister Jan 28 '23 at 16:23
  • Indeed. Say you want the first element of the list. If it *were* a `[String]` value, you could use `head` (we'll ignore the separate problem of dealing with empty lists). But since it's an `IO [String]`, you can only create yet *another* `IO` action of type `IO String` with, say, `head (readFile "foo.txt")`. Ultimately, every Haskell program is just an exercise in creating a single `IO ()` action to be executed at runtime. – chepner Jan 28 '23 at 16:24
  • @DanielMcAllister: what you'll do will look like `do lines <- readLines file; print (listOperation lines)`. – Louis Wasserman Jan 28 '23 at 19:24