Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Couldn't match expected type - Haskell Code

Tags:

haskell

I'm trying to learn Haskell, but the small bit of sample code I tried to write is running into a fairly large amount of "Couldn't match expected type" errors. Can anyone give me some guidance as to what I'm doing wrong/how I should go about this?

These are the errors, but I'm not really sure how I should be writing my code.

toDoSchedulerSimple.hs:6:14:
    Couldn't match expected type `[t0]' with actual type `IO String'
    In the return type of a call of `readFile'
    In a stmt of a 'do' block: f <- readFile inFile
    In the expression:
      do { f <- readFile inFile;
           lines f }

toDoSchedulerSimple.hs:27:9:
    Couldn't match expected type `[a0]' with actual type `IO ()'
    In the return type of a call of `putStr'
    In a stmt of a 'do' block: putStr "Enter task name: "
    In the expression:
      do { putStr "Enter task name: ";
           task <- getLine;
           return inFileArray : task }

toDoSchedulerSimple.hs:34:9:
    Couldn't match expected type `IO ()' with actual type `[a0]'
    In a stmt of a 'do' block:
      putStrLn "Your task is: " ++ (inFileArray !! i)
    In the expression:
      do { i <- randomRIO (0, (length inFileArray - 1));
           putStrLn "Your task is: " ++ (inFileArray !! i) }
    In an equation for `getTask':
        getTask inFileArray
          = do { i <- randomRIO (0, (length inFileArray - 1));
                 putStrLn "Your task is: " ++ (inFileArray !! i) }

toDoSchedulerSimple.hs:41:9:
    Couldn't match expected type `[a0]' with actual type `IO ()'
    In the return type of a call of `putStr'
    In a stmt of a 'do' block:
      putStr "Enter the task you would like to end: "
    In the expression:
      do { putStr "Enter the task you would like to end: ";
           task <- getLine;
           filter (endTaskCheck task) inFileArray }

toDoSchedulerSimple.hs:60:53:
    Couldn't match expected type `IO ()'
                with actual type `[String] -> IO ()'
    In a stmt of a 'do' block: schedulerSimpleMain
    In the expression:
      do { (getTask inFileArray);
           schedulerSimpleMain }
    In a case alternative:
        "get-task"
          -> do { (getTask inFileArray);
                  schedulerSimpleMain }

This is the code itself. I think it's fairly straightforward, but the idea is to run a loop, take input, and perform actions based off of it by calling other functions.

import System.Random (randomRIO)
import Data.List (lines)

initializeFile :: [char] -> [String]
initializeFile inFile = 
    do  f <- readFile inFile
        let parsedFile = lines f
        return parsedFile

displayHelp :: IO()
displayHelp =
    do  putStrLn "Welcome to To Do Scheduler Simple, written in Haskell."
        putStrLn "Here are some commands you might find useful:"
        putStrLn "    'help' : Display this menu."
        putStrLn "    'quit' : Exit the program."
        putStrLn "    'new-task' : Create a new task."
        putStrLn "    'get-task' : Randomly select a task."
        putStrLn "    'end-task' : Mark a task as finished."
        putStrLn "    'view-tasks' : View all of your tasks."

quit :: IO()
quit = 
    do  putStrLn "We're very sad to see you go...:("
        putStrLn "Come back soon!"

createTask :: [String] -> [String]
createTask inFileArray = 
    do  putStr "Enter task name: "
        task <- getLine
        return inFileArray:task

getTask :: [String] -> IO()
getTask inFileArray = 
    do  i <- randomRIO (0, (length inFileArray - 1))
        putStrLn "Your task is: " ++ (inFileArray !! i)

endTaskCheck :: String -> String -> Bool
endTaskCheck str1 str2 = str1 /= str2

endTask :: [String] -> [String]
endTask inFileArray =
    do  putStr "Enter the task you would like to end: "
        task <- getLine
        return filter (endTaskCheck task) inFileArray

viewTasks :: [String] -> IO()
viewTasks inFileArray =
    case inFileArray of
        [] -> do putStrLn "\nEnd of tasks."
        _ -> do putStrLn (head inFileArray)
                viewTasks (tail inFileArray)

schedulerSimpleMain :: [String] -> IO()
schedulerSimpleMain inFileArray =
    do  putStr "SchedulerSimple> "
        input <- getLine
        case input of
            "help" -> displayHelp
            "quit" -> quit
            "new-task" -> schedulerSimpleMain (createTask inFileArray)
            "get-task" -> do (getTask inFileArray); schedulerSimpleMain
            "end-task" -> schedulerSimpleMain (endTask inFileArray)
            "view-tasks" -> do (viewTasks inFileArray); schedulerSimpleMain
            _ -> do putStrLn "Invalid input."; schedulerSimpleMain

main :: IO()
main = 
    do  putStr "What is the name of the schedule? "
        sName <- getLine
        schedulerSimpleMain (initializeFile sName)

Thanks, and apologies if this isn't the correct place to be asking such a question.

like image 377
wvyar Avatar asked Nov 30 '22 05:11

wvyar


1 Answers

There are several issues with your code, which require varying levels of work to fix. In the order that I discovered them, you have...

Incorrect Types

Lots of your type signatures are incorrect. If a function does any I/O at all, it needs to wrap its return type in IO. For example, instead of

createTask :: [String] -> [String]

you need to have

createTask :: [String] -> IO [String]

which reflects the fact that createTask does I/O (it asks the user for the name of a task).

Fortunately, the fix for this is easy - just delete all your type signatures! This sounds crazy, but it can be very helpful. GHC has a powerful type inference mechanism, which means that types can often be inferred without you specifying them explicitly. In your program, all the types are simple enough to be inferred, so you can delete all your type signatures, load the module in GHCi and type e.g. :t createTask, whereupon the interpreter will tell you the inferred type (which you can then add to the source).

Operator Precedence Issues

In Haskell, function application has the tightest binding. In particular, when you write

putStrLn "Your task is: " ++ (inFileArray !! i)

this is parsed by Haskell as

(putStrLn "Your task is: ") ++ (inFileArray !! i)

which doesn't type check, since the left hand side is of type IO () and the right-hand side is of type String. This is also easy to fix. You simply need to write what you intend, which is either

putStrLn ("Your task is: " ++ (inFileArray !! i))

or

putStrLn $ "Your task is: " ++ (inFileArray !! i)

where the operator $ means "function application with the lowest possible precedence", and is often used to avoid parentheses.

Incorrect List Concatenation

After adding parentheses, your code has the line

return (inFileArray:task)

where inFileArray is of type [String] and task is of type String. Presumably you intend to add task to the end of inFileArray.

The : operator is for adding a single item to the front of a list (an O(1) operation). You can't use it to add items to the end of a list (an O(n) operation). All lists in Haskell are linked lists, so adding an item to the front of the list is fundamentally different to adding it to the end. You want either

return (task:inFileArray)

which will add the task to the front of the list, or

return (inFileArray ++ [task])

which creates a new single-element list from task and uses the list concatenation operator ++ to add it to the end of the list.

Misunderstanding do notation and >>=

This is the most fundamental misunderstanding in your code, and will require the most work to explain. Let's look at the following (highly edited) code snippet:

schedulerSimpleMain :: [String] -> IO ()                                    -- 1
schedulerSimpleMain inFileArray =                                           -- 2
    do  input <- getLine                                                    -- 3
        case input of                                                       -- 4
            "new-task" -> schedulerSimpleMain (createTask inFileArray)      -- 5
            _          -> do putStrLn "Invalid input."; schedulerSimpleMain -- 6

We already know that the type of createTask is [String] -> IO [String]. Therefore line 5 doesn't type check. The function schedulerSimpleMain expects a [String] but you are passing it an IO [String].

What you need to do is unwrap the IO layer from the result of createTask inFileArray, and pass the resulting [String] to schedulerSimpleMain (which re-wraps it in the IO layer). This is exactly what the operator >>= (pronounced bind) does. You can write this line as

createTask inFileArray >>= schedulerSimpleMain

where you can think of the >>= operator as "piping forward" the result (a bit like the Unix pipe operator) but also doing all the necessary unwrapping/rewrapping on the way.

It can be a bit tricky to use the bind operator correctly when you're just starting out, which is one of the reasons we're provided with do notation in the first place. You can write this snippet as

do newInFileArray <- createTask inFileArray
   schedulerSimpleMain newInFileArray

which is simply syntactic sugar for the code I wrote above, but is a bit more readable if you're not comfortable with the bind operator.

In line 6, you have a different but related problem. The sequencing operator ; essentially means "do the computation on the left, ignore the result, then do the computation on the right". It requires the left computation to have the type IO a and the right computation to have the type IO b (for any a and b).

Unfortunately, your right computation has the type of [String] -> IO [String], so again this line doesn't typecheck. To correct it, you just need to make sure you feed the appropriate argument to schedulerSimpleMain:

do putStrLn "Invalid input."; schedulerSimpleMain inFileArray

which now typechecks. You have this kind of error all over your code. I'm not going to detail all of the fixes for you here. I think you should try and fix it yourself first. If you're still running into problems in a day or so, I can put the corrected code on hpaste for you to study.

like image 181
Chris Taylor Avatar answered Dec 05 '22 05:12

Chris Taylor