Q:

Syntax confusion (do block)

Sorry for a poor title, feel free to edit. I can't understand what the problem is, so it might be altogether wrong. Below is the code (this is after I've done like a hundred of permutations and different sequences of let-do-if and tabulation, and I'm exhausted):
-- The last statement in a 'do' construct must be an expression
numberOfGoods :: IO String
numberOfGoods = do putStrLn "Enter year (2000-2012):\n"
                   let intYear = readYear 
                   in if (intYear < 2000 || intYear > 2012)
                         then error "Year must be withing range: 2000-2012" 
                         else
                                c <- readIORef connection
                                [Only i] <- query_ c ("select count('*')" ++
                                         "from table" ++
                                         "where ((acquisition_date <= " ++
                                         (formatDate intYear) ++
                                         ") and ((sale_date is null) or " ++
                                         "(sale_date < " ++
                                         (formatDate intYear) ++ ")))")
                                return i

readYear :: Integer
readYear = do
           year <- getLine
           read year :: Integer

Something that would meant to be so simple... I still don't understand what is wrong with the code above. Please, if you could kindly explain the source of the error, that would be great.
I did read about do, let-in and if-then-else, and I don't see any errors here from what I could understand from the manual.
Ideally, if there are alternatives, I would like very much to reduce the amount of the wasted white space on the left.
Thank you.

A:

readYear is not an Integer, it's an IO action that can be run to read input and convert the input to an integer -- in other words, IO Integer. And as it's an IO action, you'll need a return to use whatever read year as result of getYear. That is:
getYear :: IO Integer
getYear = do year <- getLine
             return (read year)

This also means you use it like intYear <- readYear instead of using let (well, you could, but you'd store the IO action instead of running it, and the type of intYear would be wrong). That is:
numberOfGoods :: IO String
numberOfGoods = do putStrLn "Enter year (2000-2012):\n"
                   intYear <- readYear
                   ...

do does not extend over if, rather you need to start again with do if you want a sequence of actions in the then or else branch. That is:
                     else
                            c <- readIORef connection
                            ...
                            return i

should be roughly:
                     else do c <- readIORef connection
                             ...
                             return i

As for reducing whitespace, consider pushing the validation logic into readYear. Implementing this is left as an exercise to the reader ;)
As an aside, you don't need in when using let in a do block (but only there!), you can simply state:
do do_something
   let val = pure_compuation
   something_else_using val

