Search code examples
parsinghaskelltypesreadfile

Why does Haskell's type system have a problem with my use of readFile?


I have some troubles with Haskell's type system.

Situation:

  • Following program is taking list of filenames on the command-line
  • For each filename its contents is read using the function readFile
  • Contents of each file is passed to inputParser (from Parsec library)
  • Rest is not so important
  • Main problem is in function read_modules
  • First two statements of the do expression are invalid in Haskell's type system
  • Problem is conflict between [String] vs IO String vs [Char] vs ...
  • Function parse should take a String but when it gets it, it wants an IO String suddenly (as the same argument), otherwise it wants a String

What do I want:

  1. Read each file's content
  2. Pass that content to the parse function as third argument

Here is the code:

module Main where

import System.IO
import System.Environment
import Text.ParserCombinators.Parsec
import InputParser
import Data

usage :: IO ()
usage = putStrLn "Usage: x file file file option"

parse_modules :: String -> [Char] -> Either ParseError [Module]
parse_modules filename input = parse inputParser filename input

read_modules :: [String] -> [Module]
read_modules [] = []::[Module]
read_modules (filename:rest) =
  do
    content <- readFile filename -- HERE is the problem
    modules <- case parse_modules filename content of -- HERE is problem too
      Left error -> do
        putStr "parse error at "
        print error
      Right out -> out ++ (read_modules rest)
    return modules

use :: [String] -> IO ()
use args =
  do
    init <- last args
    filenames <- take (length args - 1) args
    modules <- read_modules filenames
    return ()

main :: IO ()
main = do args <- getArgs
          if length args < 2
            then usage
            else use args

Here are the errors GHC outputs:

ghc --make -o x.hs input-parser.hs data.hs
[3 of 3] Compiling Main             ( x.hs, x.o )

x.hs:19:4:
    Couldn't match expected type `IO String'
           against inferred type `[String]'
    In a stmt of a 'do' expression: content <- readFile filename
    In the expression:
        do content <- readFile filename
           modules <- case parse_modules filename content of {
                        Left error -> do ...
                        Right out -> out ++ (read_modules rest) }
           return modules
    In the definition of `read_modules':
        read_modules (filename : rest)
                       = do content <- readFile filename
                            modules <- case parse_modules filename content of {
                                         Left error -> ...
                                         Right out -> out ++ (read_modules rest) }
                            return modules
-- THIS ERROR is somewhat not important
x.hs:30:4:
    Couldn't match expected type `[Char]'
           against inferred type `IO Char'
      Expected type: String
      Inferred type: IO Char
    In a stmt of a 'do' expression: init <- last args
    In the expression:
        do init <- last args
           filenames <- take (length args - 1) args
           modules <- read_modules filenames
           return ()
make: *** [x] Error 1

What is the problem:

  • I cannot understand what I should pass where - I kind of know what I want, but I don't get the syntax or the style.
  • I am new to Haskell.
  • Haskell's types...

What are the questions:

  • How do I fix the presented type issue?
  • What should I put into parse - what readFile gives me?
  • Are the types compatible?
  • Isn't there need for some type of conversion?

Relevant weblinks:

Thank you all for your hints and comments.


Solution

  • Here's what's causing the other error that you said was less important:

    use :: [String] -> IO ()
    use args =
      do
        init <- last args
    

    The <- operator is used within a do block to extract something contained in a monad (in this case, IO) so that you can work with the actual value trapped inside. But, args here is of type [String], not IO [String], so you don't need to do that; you already pulled the argument list out of IO with arg <- getArgs in main.

    If you want to assign a non-monadic value to a temporary variable inside a do block, use let instead, like this:

    let x = last args
    

    It looks like you're making the same mistake in several other places as well, not just that line. Having to treat monadic vs. non-monadic values differently like that, when you just want to make a temporary variable inside your function, is an easy thing to get confused about for someone new to the language.

    By the way, init is the name of a function in the standard library, so you might want to use a different variable name.