im totally beginner at Haskell i came from js environment , i have a simple array students
into which i want to push some student objects but sadly Haskell does not support objects ( if there is a way i can do it please guide me ) so tried to make a simple program that reads the user input (array) and push that into the students
array , here is what i have tried :
main :: IO()
main = do
let students = []
studentArray <- getLine
students ++ studentArray
print(students)
but the following error is thrown : Couldn't match type `[]' with `IO'
First, you might want to take a look at the resources in this SO answer. If you haven't already worked through the tutorials in the section for "Absolute Beginner"s, that would be a good starting point.
See, for other programming languages, it's usual to start with programs that print "Hello, world!"
on the screen or that -- like your example -- read lists of students from the console and print them back out. For Haskell, it usually makes more sense to work with quite different types of programs at first. For example, the tutorial "Learn You a Haskell for Great Good" doesn't get to "Hello, world!"
until Chapter 9, and the "Happy Learn Haskell Tutorial" doesn't get to it until Chapter 15 (and then it only covers output -- input doesn't come until Chapter 20).
Anyway, back to your example. The problem is with the line students ++ studentArray
. This is an expression that concatenates the empty list students = []
with the value of studentArray
, which is a String
retrieved by getLine
. Since a String
is just a list of characters, the empty list is just the empty string, so you are writing the rough equivalent of the JavaScript function:
function main() {
var students = "" // empty list is just empty string
var studentArray = readLineFromSomewhere()
students + studentArray // concatenate strings and throw away result
console.log(students) // print the empty string
}
In JavaScript, this would run and print the empty string because the students + studentArray
line doesn't do anything. In Haskell, this doesn't type check because Haskell expects all (non-let
) lines in this do
block to be I/O actions:
main :: IO () -- signature forces `do` block to be I/O
main = do
let students = [] -- "let" line is okay
studentArray <- getLine -- `getLine` is IO action
students ++ studentArray -- **NOT** IO action: it's a String AKA [Char]
print students -- `print students` is IO action
Because students ++ studentArray
is a String
/ [Char]
/ list of characters appearing in an IO
do-block, Haskell expected an IO something
but found a [something]
, and it's complaining that the types of lists ([]
) and IO
don't match.
But, even if you could fix this, it wouldn't help because, like the JavaScript +
operator and unlike the JavaScript push
method, the Haskell ++
operator doesn't modify its arguments, so a ++ b
only returns the concatenation of a
and b
without changing a
or b
.
This is a pretty fundamental aspect of Haskell that makes it different from most other programming languages. By default, Haskell variables are immutable. Once they are assigned, at the top level, by a let
statement, or assigned as arguments in a function call, they don't change value. (In fact, since they aren't really "variable", we usually call them "bindings" instead of "variables".) So, if you want to build up a list of students
in Haskell, you don't start by assigning an empty list to a variable and then trying to modify that variable by adding students. Instead, you either do it all at once:
import Control.Monad (replicateM)
main :: IO ()
main = do
putStrLn "Enter number of students:"
n <- readLn
putStrLn $ "Enter " ++ show n ++ " student names:"
students <- replicateM n getLine
putStrLn $ "List of students:"
print students
or use function calls to simulate variables by re-binding an identifier to an updated value:
main :: IO ()
main = do
putStrLn "Enter number of students:"
n <- readLn
putStrLn $ "Enter " ++ show n ++ " student names:"
students <- getStudents n []
print students
getStudents :: Int -> [String] -> IO [String]
getStudents 0 studentsSoFar = return studentsSoFar
getStudents n studentsSoFar = do
student <- getLine
getStudents (n-1) (studentsSoFar ++ [student])
See here how getStudents
is originally called with the total number of students and an initial empty list (which get bound to n
and studentsSoFar
respectively in the getStudents
call), and then uses recursion to re-bind n
and studentsSoFar
to decrement n
while "pushing" more students on to studentsSoFar
.
By itself, the expression studentsSoFar ++ [student]
would do nothing, but by using it in a recursive getStudents
call, this new value can be re-bound as studentsSoFar
to simulate changing the value of this "variable".
Anyway, this is a pretty standard approach in Haskell, but it's maybe unusual for folks coming from JavaScript or other languages, so it's worth working through tutorials that cover recursion before input/output... like "Learn You" (recursion in Chapter 5, I/O in Chapter 9) or "Happy Learn" (recursion in Chapter 10, I/O in Chapters 15 and 20) or "Haskell Programming from First Principles" (recursion in Chapter 8, I/O in Chapter 29) or "Programming in Haskell" (recursion in Chapter 6, I/O in Chapter 10). I'm sure you see the pattern here.