简体   繁体   中英

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.

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

You need a new do for every block of monadic functions: simply writing functions in a row has no meaning, regardless of whether they're monadic or pure. And everything where the value comes from the IO monad must itself give its return value in the monad.

numberOfGoods :: IO String
numberOfGoods = do putStrLn "Enter year (2000-2012):\n"  -- why extra '\n'?
                   intYear <- readYear   -- readYear expects user input <- must be monadic
                   if (intYear < 2000 || intYear > 2012)
                         then error "Year must be withing range: 2000-2012" 
                         else do
                                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 :: IO Integer
readYear = do
           year <- getLine
           return $ read year :: Integer


Why is an extra do needed...

Well, the thing with do in Haskell is that it's really just syntactic sugar. Let's simplify your function a little

 nOG :: IO String nOG = do putStrLn "Prompt" someInput <- inputSth if condition someInput then error "Bloap" else do c <- inputSthElse [only] <- query_ c return only 

what this actually means is

 nOG :: IO String nOG = putStrLn "Prompt" >> inputSth >>= (\\someInput -> if condition someInput then error "Bloap" else inputSthElse >>= (\\s -> query_ c >>= (\\[only] -> return only ) ) ) 

Where you should be able to see that if behaves in exactly the same way as it does in a pure functional expression like shade (r,g,b) = if g>r && g>b then "greenish" else "purpleish" . It doesn't in any way "know" about all the IO monad stuff going on around it, so it can't infer that there should again be a do block in one of its branches.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM