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.
There are several issues with your code, which require varying levels of work to fix. In the order that I discovered them, you have...
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).
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.
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.
>>=
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With