Syntax confusion (do block)

删除回忆录丶 提交于 2020-01-16 04:37:19

问题


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.


回答1:


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



回答2:


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.



来源:https://stackoverflow.com/questions/9573388/syntax-confusion-do-block

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!