1

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'

Will Ness
  • 70,110
  • 9
  • 98
  • 181
  • 2
    What do you think `students ++ studentArray` does? – Joseph Sible-Reinstate Monica Jan 25 '20 at 17:40
  • 1
    By definition, `students ++ studentArray` is the same as `[] ++ studentArray` -- you could write that instead and nothing would change. Similarly, the last line is equivalent to `print []`. This is clearly not what you want: you are trying to _mutate_ a variable in Haskell, which was designed to prevent you to perform mutation. You need to rewrite your code following a purely functional, rather than imperative, approach. – chi Jan 25 '20 at 19:17
  • [how to increment a variable in functional programming](https://stackoverflow.com/q/11922134/849891). – Will Ness Jan 26 '20 at 08:30

1 Answers1

7

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.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71