0

I'm just learning some light haskell code, but the tutorials I have been following don't mention this error I continue to get when trying to run any code with do and I/O type. GHCI seems to think its some type error. Here is my code:

main = do
  putStrLn "This program can find all odd numbers between 1 and a desired number. Please enter a number."
  user <- getLine
  if (read user) < 1
      then
        do putStrLn "Improper input specified. 10 will be chosen as     default."
            putStrLn show oddsort 10 [] ++ "Is the list of odd numbers from 1 to 10."
    else
      do putStrLn show (oddsort (read user) [])

oddsort x xs
  |x == 1 = 1:xs
  |x `mod` 2 == 0 = oddsort (x-1) xs
  |otherwise = oddsort (x - 2) (x:xs)
Sagarmatha
  • 79
  • 6
  • 1
    Posting the full error message is very important on issues like this. Otherwise, people have to guess, or to spend more effort in copying your code into a file, and see the error. This generally results in fewer, less precise answers. – chi Jan 04 '19 at 21:34
  • Right although in fairness, in this case the error message really wouldn't have helped finding the error more quickly. But in the future, do always add error messages even if they are clunky. – leftaroundabout Jan 04 '19 at 21:41
  • if you use explicit delimiters `do { ... ; ... }`, you will never have to worry about code being broken by accidental indentation. – Will Ness Jan 04 '19 at 23:21

3 Answers3

4

There are two mistakes in your code:

  1. Indentation. Always make sure that all the lines the belong together in a do block are indented to the same level

    main = do
     ⋮putStrLn "This program..."
     ⋮user <- getLine
     ⋮if (read user) < 1
     ⋮   then
     ⋮      do⋮putStrLn "Improper..."
     ⋮        ⋮█putStrLn ...
     ⋮  else
     ⋮    do putStrLn ...
    

    Notice there's one space to many before the third putStrLn.
    Indentation is also important for many other Haskell constructs (where, case, class...). As a matter of style, I would also make it a habit to align else/then, though that's actually not required by the standard. I.e.

    main = do
     ⋮putStrLn "This program..."
     ⋮user <- getLine
     ⋮if (read user) < 1
     ⋮⋮then do
     ⋮⋮ ⋮putStrLn "Improper..."
     ⋮⋮ ⋮putStrLn ...
     ⋮⋮else do
     ⋮⋮ ⋮putStrLn ...
    
  2. Although putStrLn may look like a “command at the begin of the line” that's always parsed as such, this is not the case – it's just an ordinary function. Therefore, putStrLn show oddsort 10 [] ++ "Bla" is parsed as

      (putStrLn show oddsort 10 []) ++ "Bla"
    

    i.e. putStrLn is applied to the arguments show, oddsort, 10 and []. Obviously it has no idea what to do with all of that, i.e. this is a massive type error! What you actually want is

      putStrLn (show (oddsort 10 []) ++ "Bla")
    

    which can also be written with the “parenthisation operator” $:

      putStrLn $ show (oddsort 10 []) ++ "Bla"
    
leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • Thank you so extremely much! This answered everything I needed to know and more, I can't believe my tutorial left out that little fact about putStrLn and how it works. The indentation was just sloppy rushed work on my part and I can't believe I missed it. – Sagarmatha Jan 04 '19 at 21:39
1

I'll focus on these lines

do putStrLn "Improper input specified. 10 will be chosen as     default."
    putStrLn show oddsort 10 [] ++ "Is the list of odd numbers from 1 to 10."

Issues:

  1. wrong indentation: block entries should start exactly on the same column

  2. the second line parses as (putStrLn show oddsort 10 []) ++ ("Is the list of odd numbers from 1 to 10.") which is wrong, since you can't ++ a non-list like putStrLn show oddsort 10 []. You need parentheses.

  3. putStrLn show oddsort 10 [] calls function putStrLn with 4 arguments: show, oddsort, 10, and []. This is not what you want, so you need more parentheses.

There are probably more issues, but these are the most evident ones.

General recommendations: add type signatures to all your top-level bindings, like oddsort. This will help GHC to produce better type error messages. Further, turning on warnings with -Wall can also help detecting some issues.

chi
  • 111,837
  • 3
  • 133
  • 218
1

Here is a working version of your code:

oddsort :: Int -> [Int] -> [Int]
oddsort x xs
  |x == 1 = x : xs
  |x `mod` 2 == 0 = oddsort (x - 1) (x:xs)
  |otherwise = oddsort (x - 2) (x:xs)

main = do
    putStrLn "This program can find all odd numbers between 1 and a desired number. Please enter a number."
    user <- getLine
    if (read user :: Int) < 1
    then putStrLn $ "Improper input specified. 10 will be chosen as     default. \n" ++ ((show $ oddsort 10 []) ++ " Is the list of odd numbers from 1 to 10.")
    else putStrLn $ show $ (oddsort (read user :: Int) [])

I made a few changes to your code and listed them in order.

The first thing I did was add a type signature to oddsort because otherwise the type signature is left to the compiler to inference (guess) the type and you don't really want to do that with functions, why? well check this.

Then I changed if (read user) < 1 to if (read user :: Int) < 1, because again you're leaving it up to the compiler to guess the return of read and read returns a specific type once you've defined the signature for the return type :: Int which specifies that the return type of read is an Int.

The last change was in then clause, I simply removed your second putStrLn statement and concatenated the result of (show $ oddsort 10 []) to "Is the list of odd numbers from 1 to 10.") because by having putStrLn show oddsort 10 [] GHC will take everything after show as an argument and putStrLn can only take strings and only 1 argument at that. You can see this on the repl by typing :t show. Also I concatenated them to 1 string because you really only need 1 but 2 looks neater so keep it that way but add parenthesis to your second putStrLn so there is only 1 argument. You can also use $ operator for this.

Bargros
  • 521
  • 6
  • 18