3

I'm pretty new to Haskell, and am trying to simply read a file into a list of strings. I'd like one line of the file per element of the list. But I'm running into a type issue that I don't understand. Here's what I've written for my function:

readAllTheLines hdl = (hGetLine hdl):(readAllTheLines hdl)

That compiles fine. I had thought that the file handle needed to be the same one returned from openFile. I attempted to simply show the list from the above function by doing the following:

displayFile path = show (readAllTheLines (openFile path ReadMode))

But when I try to compile it, I get the following error:

filefun.hs:5:43: Couldn't match expected type 'Handle' with actual type 'IO Handle' In the return type of a call of 'openFile' In the first argument of 'readAllTheLines', namely '(openFile path ReadMode)' In the first argument of 'show', namely '(readAllTheLines (openFile path ReadMode))'

So it seems like openFile returns an IO Handle, but hGetLine needs a plain old Handle. Am I misunderstanding the use of these 2 functions? Are they not intended to be used together? Or is there just a piece I'm missing?

2
  • Have you already had a look at a good Haskell tutorial like Learn you a Haskell? Its chapter about Input and Output will make it much easier to understand what the difference between some value and a value in IO is. Commented Sep 10, 2013 at 5:16
  • I just found that particular tutorial tonight. I had been reading the Haskell wikibook, but I'm finding it getting confusing in later chapters. Thanks for the pointer! Commented Sep 10, 2013 at 5:23

2 Answers 2

6

Use readFile and lines for a better alternative.

readLines :: FilePath -> IO [String]
readLines = fmap lines . readFile 

Coming back to your solution openFile returns IO Handle so you have to run the action to get the Handle. You also have to check if the Handle is at eof before reading something from that. It is much simpler to just use the above solution.

import System.IO

readAllTheLines :: Handle -> IO [String]
readAllTheLines hndl = do
   eof <- hIsEOF hndl
   notEnded eof
  where notEnded False =  do
          line <- hGetLine hndl
          rest <- readAllTheLines hndl
          return (line:rest)
        notEnded True = return []

displayFile :: FilePath -> IO [String]
displayFile path = do
  hndl <- openFile path ReadMode
  readAllTheLines hndl 
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! I'll need to read up on both lines and readFile, but I think I get the basic concept.
0

To add on to Satvik's answer, the example below shows how you can utilize a function to populate an instance of Haskell's STArray typeclass in case you need to perform computations on a truly random access data type.


Code Example

Let's say we have the following problem. We have lines in a text file "test.txt", and we need to load it into an array and then display the line found in the center of that file. This kind of computation is exactly the sort situation where one would want to use a random access array over a sequentially structured list. Granted, in this example, there may not be a huge difference between using a list and an array, but, generally speaking, list accesses will cost O(n) in time whereas array accesses will give you constant time performance.

First, let's create our sample text file:

test.txt

This
is
definitely
a
test.

Given the file above, we can use the following Haskell program (located in the same directory as test.txt) to print out the middle line of text, i.e. the word "definitely."

Main.hs

{-# LANGUAGE BlockArguments #-} -- See footnote 1

import Control.Monad.ST (runST, ST)
import Data.Array.MArray (newArray, readArray, writeArray)
import Data.Array.ST (STArray)
import Data.Foldable (for_)
import Data.Ix (Ix) -- See footnote 2

populateArray :: (Integral i, Ix i) => STArray s i e -> [e] -> ST s () -- See footnote 3
populateArray stArray es = for_ (zip [0..] es) (uncurry (writeArray stArray))

middleWord' :: (Integral i, Ix i) => i -> STArray s i String -> ST s String
middleWord' arrayLength = flip readArray (arrayLength `div` 2)

middleWord :: [String] -> String
middleWord ws = runST do
  let len = length ws
  array <- newArray (0, len - 1) "" :: ST s (STArray s Int String)
  populateArray array ws
  middleWord' len array
  
main :: IO ()
main = do
  ws <- words <$> readFile "test.txt"
  putStrLn $ middleWord ws

Explanation

Starting with the top of Main.hs, the ST s monad and its associated function runST allow us to extract pure values from imperative-style computations with in-place updates in a referentially transparent manner. The module Data.Array.MArray exports the MArray typeclass as an interface for instantiating mutable array data types and provides helper functions for creating, reading, and writing MArrays. These functions can be used in conjunction with STArrays since there is an instance of MArray defined for STArray.

The populateArray function is the crux of our example. It uses for_ to "applicatively" loop over a list of tuples of indices and list elements to fill the given STArray with those list elements, producing a value of type () in the ST s monad.

The middleWord' helper function uses readArray to produce a String (wrapped in the ST s monad) that corresponds to the middle element of a given STArray of Strings.

The middleWord function instantiates a new STArray, uses populateArray to fill the array with values from a provided list of strings, and calls middleWord' to obtain the middle string in the array. runST is applied to this whole ST s monadic computation to extract the pure String result.

We finally use our middleWord function in main to find the middle word in the text file "test.txt".

Further Reading

Haskell's STArray is not the only way to work with arrays in Haskell. There are in fact Arrays, IOArrays, DiffArrays and even "unboxed" versions of all of these array types that avoid using the indirection of pointers to simply store "raw" values. There is a page on the Haskell Wikibook on this topic that may be worth some study. Before that, however, looking at the Wikibook page on mutable objects may give you some insight as to why the ST s monad allows us to safely compute pure values from functions that use imperative/destructive operations.

Footnotes

1 The BlockArguments language extension is what allows us to pass a do block directly to a function without any parentheses or use of the function application operator $.
2 As suggested by the Hackage documentation, Ix is a typeclass mainly meant to be used to specify types for indexing arrays.
3 The use of the Integral and Ix type constraints may be a bit of overkill, but it's used to make our type signatures as general as possible.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.