1

I have input data intended for my yet-to-be-written Haskell applications, which reside in a file. I don't update the file. I just need to read the file and feed it into my Haskell function which expects a list of strings. But reading the file of course yields IO data objects. I have learned that using the <- operation can "take out" somehow the strings packed in an IO structure, so I tried this attempt:

run :: [String]
run = do
  datadef_content <- readFile "play.txt" -- yields a String
  let datadef = lines datadef_content -- should be a [String]
  return datadef

I placed this into a file play.hs and loaded it from ghci by

:l play

To my surprise, I got the error message for the readFile line

 Couldn't match type ‘IO’ with ‘[]’
 Expected type: [String]
   Actual type: IO String

and for the return the error message

 Couldn't match type ‘[Char]’ with ‘Char’
 Expected type: [String]
   Actual type: [[String]]

The first seems to indicate that I couldn't get rid of the IO, and the last message seems to suggest, that lines would return a list of list of strings, which also doesn't make sense to me.

How can I do this correctly?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
user1934428
  • 19,864
  • 7
  • 42
  • 87
  • You indeed can not get rid of `IO` (or at least you should try to never do that). There is `unsafeIO`, but that will normally generate more trouble than it solves. – Willem Van Onsem Feb 02 '20 at 18:50
  • 1
    [`Do`-notation explained, in vivid colors](https://stackoverflow.com/a/11326549/849891). – Will Ness Feb 02 '20 at 20:56
  • Don't try to `run :: [String]`. Instead, take the function `foo :: [String] -> blah` that takes the contents of `run` as an argument and convert it to `wrappedFoo :: IO [String] -> blah` using either `fmap` or `(=<<)` as appropriate, then apply it to `run :: IO [String]`. – Daniel Wagner Feb 02 '20 at 21:15
  • 1
    Haskell forces you to declare that you perform IO _in the type_, if you actually perform IO. Declaring `run :: [String]` amounts to promising that `run` does no IO, and is a "pure" constant list of strings. If you want to do IO, you need to declare `run :: IO [String]`, telling the compiler that this depends on IO, and that it can be executed multiple times, possibly with different results (if e.g. `"play.txt"` changes its contents). – chi Feb 02 '20 at 22:58

1 Answers1

5

You declare run to be a [String] value. But return is not a keyword that provides the return value of a function; it is a function, with type Monad m => a -> m a. return datadef produces a value of type IO [String], which becomes the return value of the function.

The solution is to provide the correct return type for run:

run :: IO [String]
run = do
    ...

run can also be defined more succinctly as

run = fmap lines (readFile "play.txt")

Though the do syntax suggests is, there is no way to pull a value out of an IO action; all you can do is "push" the call to lines into the action.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • Thank you; I will try it. Do you happen to know a source where I can learn about the meaning of `fmap`? The best i could google is [this description of Data.Functor](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Functor.html), and it is pretty terse and I can't quite understand how it explains your example. – user1934428 Feb 05 '20 at 12:38
  • I tried your approach. Now the declaration of `run` is fine, but does it mean that I have to do all my calculation which uses the content of the file inside the `do` block? – user1934428 Feb 09 '20 at 12:42
  • I found [this](http://book.realworldhaskell.org/read/io.html) webpage which seems to answer my question. – user1934428 Feb 09 '20 at 13:18