【问题标题】:Syntax confusion (do block)语法混乱(do block)
【发布时间】:2012-03-05 20:19:55
【问题描述】:

抱歉标题不佳,请随时编辑。我不明白问题是什么,所以它可能是完全错误的。下面是代码(这是在我完成了一百个排列和不同的 let-do-if 和制表序列之后,我已经筋疲力尽了):

-- 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 的内容,并且从手册中可以理解的内容来看,这里没有任何错误。

理想情况下,如果有替代品,我非常希望减少左侧浪费的空白数量。

谢谢。

【问题讨论】:

  • 使用 IO 定义,readYear 不可能是整数。

标签: haskell syntax indentation


【解决方案1】:

readYear 不是Integer,它是一个IO 操作,可以运行以读取输入并将输入转换为整数——换句话说,IO Integer。因为它是一个IO 操作,所以你需要一个return 来使用read year 作为getYear 的结果。那就是:

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

这也意味着您像 intYear &lt;- readYear 一样使用它,而不是使用 let(好吧,您可以,但是您将存储 IO 操作而不是运行它,并且 intYear 的类型是错误的)。那就是:

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

do 不会扩展到if,如果您想要在thenelse 分支中执行一系列操作,则需要从do 重新开始。那就是:

                     else
                            c <- readIORef connection
                            ...
                            return i

应该是:

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

至于减少空格,请考虑将验证逻辑推入readYear。实现这一点留给读者作为练习;)

顺便说一句,在do 块中使用let 时不需要in(但只有在那里!),您可以简单地声明:

do do_something
   let val = pure_compuation
   something_else_using val

【讨论】:

  • @wvxvw 通常,您总是需要do,除非它是单个表达式(do error "..." 具有冗余)或者您已经立即位于do 块内.所以letwhere等一般都需要do。当您了解 do 符号去糖的含义时,它可能更有意义(但为此您需要真正了解 monad - 嗯,还有一个 ^^ 的理由)。 let“快捷方式”只有在紧接在 do 块内时才有可能 - 它只是用于特定目的的特殊语法糖。
  • @wvxvw 从不使用do 是不合适的......我写了你的功能看起来没有它的糖,你会同意它不是特别漂亮。 do 实际上真的很有帮助,几乎不会造成任何麻烦,只要没有正确掌握底层的 monad 结构,就会有点混乱。
【解决方案2】:

您需要一个新的do 来为每个单子函数块:简单地连续编写函数没有任何意义,无论它们是单子函数还是纯函数。并且值来自 IO monad 的所有内容本身都必须在 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


为什么需要额外的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&gt;r &amp;&amp; g&gt;b then "greenish" else "purpleish" 等纯函数表达式中的行为方式完全相同。它不以任何方式“知道”它周围发生的所有 IO monad 东西,因此它不能推断在它的一个分支中应该再次有一个 do 块。

【讨论】:

  • do 不是一个块,它是一个表达式。 a <- foo b <- bar return a 不是一个单一的表达式,它根本就不是一个表达式,因为
猜你喜欢
  • 1970-01-01
  • 2017-06-11
  • 2020-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多