Синтаксическая путаница (блокировка do) - PullRequest
1 голос
/ 06 марта 2012

Извините за плохой заголовок, не стесняйтесь редактировать. Я не могу понять, в чем проблема, так что это может быть совершенно неправильно. Ниже приведен код (это после того, как я выполнил сотню перестановок и различные последовательности операций "давай-сделай-если" и "табуляция", и я исчерпан):

-- 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

Что-то, что могло бы быть таким простым ... Я до сих пор не понимаю, что не так с кодом выше. Пожалуйста, если вы любезно объясните источник ошибки, это было бы здорово. Я читал про do, let-in и if-then-else, и я не вижу здесь никаких ошибок из того, что я мог понять из руководства.

В идеале, если есть альтернативы, я бы очень хотел уменьшить количество потерянного пустого пространства слева.

Спасибо.

Ответы [ 2 ]

4 голосов
/ 06 марта 2012

readYear - это не Integer, это действие IO, которое можно запустить для чтения ввода и преобразования его в целое число - другими словами, IO Integer.И поскольку это действие IO, вам понадобится return, чтобы использовать read year в результате getYear.То есть:

getYear :: IO Integer
getYear = do year <- getLine
             return (read year)

Это также означает, что вы используете его как intYear <- readYear вместо let (ну, вы могли бы, но вы бы сохранили действие ввода-вывода вместо его запуска, и типиз intYear было бы неправильно).То есть:

numberOfGoods :: IO String
numberOfGoods = do putStrLn "Enter year (2000-2012):\n"
                   intYear <- readYear
                   ...

do не распространяется на if, скорее вам нужно начать заново с do, если вы хотите последовательность действий в ветви then или else,То есть:

                     else
                            c <- readIORef connection
                            ...
                            return i

должно быть примерно:

                     else do c <- readIORef connection
                             ...
                             return i

Что касается сокращения пробелов, рассмотрите возможность вставки логики проверки в readYear.Реализация этого остается в качестве упражнения для читателя;)

В качестве отступления, вам не нужно in при использовании let в блоке do (но только там!), Вы можетепросто заявите:

do do_something
   let val = pure_compuation
   something_else_using val
0 голосов
/ 06 марта 2012

Вам нужен новый do для каждого блока монадических функций: простое написание функций в строке не имеет смысла, независимо от того, являются ли они монадическими или чистыми.И все, где значение берется из монады IO, должно само давать свое возвращаемое значение в монаде.

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


Зачем нужна дополнительная do ...

Что ж, в Haskell с do то, что на самом деле это просто синтаксический сахар.Давайте немного упростим вашу функцию

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

, что на самом деле означает

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

Где вы должны увидеть, что if ведет себя точно так же, как и вчисто функциональное выражение типа shade (r,g,b) = if g>r && g>b then "greenish" else "purpleish".Он никоим образом не «знает» обо всех вещах IO, происходящих вокруг него, поэтому он не может сделать вывод, что в одной из его ветвей снова должен быть блок do.

...