【问题标题】:Do statement under a where clause在 where 子句下做语句
【发布时间】:2018-11-21 02:11:52
【问题描述】:

我正在尝试使用 <- 绑定将 IO [String] 转换为 [String];但是,我需要在 where 语句下使用 do 块来执行此操作,但 Haskell 一直抱怨缩进。代码如下:

decompEventBlocks :: IO [String] -> IO [[String]]
decompEventBlocks words
 | words' /= [] = block : (decompEventBlocks . drop $ (length block) words')
 | otherwise = []
  where 
   do
    words' <- words
    let block = (takeWhile (/="END") words')

这是什么原因?我们如何在 where 语句中使用 do 块?而且,有没有机会在守卫面前发表一些声明?

【问题讨论】:

  • do-blocks 不是语句,它们是值。你认为这应该做什么?就像我在你上一个问题中所说的那样,在使用 monad 之前学习它们!
  • 处理缩进,可以使用do { x ; y ; z }显式语法。
  • 将你的函数更改为[String] -&gt; [[String]],然后只需使用fmap 将其提升到IO monad。
  • @onurcanbektas Monad 不是'语言的内部工作',它们是 Haskell 的一个重要特性,定义其 I/O 约束.如果不了解 monad,您将无法编写出好的 Haskell。了解单子!
  • @AJFarmar 虽然是真的,但 do 表示法可以自己掌握,作为 DSL 本身,几乎不需要遵循什么规则。这可能是掌握的第一步,更小的步骤。 (另外,包括所有可选的括号、大括号和分号(do、case、$、...)可以真正帮助初学者,因为它们可能仍然存在语法上的任何额外不确定性)。

标签: haskell scope functional-programming monads do-notation


【解决方案1】:

记号用于写一般形式的表达式

ex :: Monad m => m t
let ex = do 
          {  x <- foo         -- foo        :: Monad m => m a,   x :: a
          ;  y <- bar x       -- bar  x     :: Monad m => m b,   y :: b
          ;  z <- baz x y     -- baz  x y   :: Monad m => m c,   z :: c
          ;  quux x y z       -- quux x y z :: Monad m => m t
          }

注意所有ms 都是相同的,abc、... 可以不同,尽管最后一个 do 中的t子表达式的类型和整个 do 表达式的类型相同。

do 符号变量被称为“绑定”&lt;- 构造。它们在引入时进入范围(&lt;- 的左侧),并保留在所有后续 do 子表达式的范围内。

一个可用于任何单子的内置单子表达式是return :: Monad m =&gt; a -&gt; m a。因此x &lt;- return v 绑定 xv,这样x 将在后续的子表达式中可用,并且将具有v 的值。

所有do变量都限制到那个do块,不能在外面使用它。每个变量的作用域是同一个do 块中的所有代码,位于变量绑定之后的 / 之下。

这也意味着&lt;-'s 是一个非递归绑定,因为该变量不能在其右侧和左侧一样:它将是两个不同变量在这种情况下,使用相同的名称,并且右侧的变量必须已在该点上方的某个位置建立。

这里有一些通用模式:

do { _ <- p ; _ <- q ; r }    ===   do { p ; q ; r }
do { x <- p ; return x }      ===   do { p }          ===   p
do { x <- return v ; foo x }  ===   do { foo v }      ===   foo v
do { p ; q ; r }              ===   do { p ; do { q ; r } }
                              ===   do { do { p ; q } ; r }
do { x <- p ;                 ===   do { x <- p ;
     y <- q x ;                          z <- do { y <- q x ;
     return (foo x y) }                            return (foo x y) } ;
                                         return z }

所有Monad m =&gt; m a 表达式都是这样的表达式,因此特别是可以是if - then - else 表达式,其结果分支和替代分支都是相同的单子类型(这常常让初学者感到困惑):

    do { x <- p ;
         y <- if (pred x) then (foo x) else (bar x) ;
         return (baz x y) }

更新: monad 的要点之一是其将效果与纯计算完全分离。一旦进入一个monad,你就不能“退出”。一元计算可以使用纯计算,但反之则不行。

【讨论】:

    【解决方案2】:

    作为 AJFarmar 回答的一个稍微不同的角度:where 中唯一可以拥有的东西是声明。 do 块不是声明,它们是表达式。 IE。这与您尝试编写where 2+5 相同。如果你想在where 中声明block,它必须

    where
      // can have other declarations, even mutually recursive
      block = ...
    

    【讨论】:

    • 那么,words' &lt;- words 不是声明吗?但为什么?
    • @onurcanbektas 因为words' &lt;- words; bwords &gt;&gt;= \words' -&gt; b 的语法糖。我在回答中解释了这一点。
    【解决方案3】:

    记住:do-blocks 是 syntactic sugar 用于一元符号。这意味着以下适用:

    do {a; b} = a >> b
    dp {a <- b; c} = b >>= \a -> c
    

    换句话说,当使用do-notation 时,您实际上是在产生值。这就是为什么你不能只在 where 语句的顶层有一个 do-block 。

    解决这个问题的方法是将函数放入do-block:

    decompEventBlocks :: IO [String] -> IO [[String]]
    decompEventBlocks words = do
        -- We unwrap the IO [String], but we keep it in the do-block,
        -- because it must be kept in a monadic context!
        words' <- words 
        let block = (takeWhile (/="END") words')
        -- This is equivalent to the guards you had in your function.
        -- NB return :: Monad m => a -> m a, to keep it in a monadic context!
        if not $ null words'
            then do 
              -- Since the recursion is monadic, we must bind it too:
              rest <- decompEventBlocks $ return $ drop (length block) words'
              return $ block : rest
            else return []
    

    要了解 monad、do-notation、&gt;&gt;=&gt;&gt;,我强烈建议阅读 the LYAH chapters,以便在尝试更多的 monadic 代码之前更好地理解。

    【讨论】:

    • 我只是直接复制粘贴代码,但它会抛出 2 个错误,因为在 (:) 中应该是 [String],而不是 IO [String];至少 ghci 是这么说的。
    • 啊,这是原来定义的缺陷,现在更新。 (现已更新)
    • 但是 decompEventBlocks 的参数仍然在 rest
    • 顺便说一句,代码给出了错误(这是我上面问题的原因。)
    • @onurcanbektas 逐字运行此代码时没有错误。如果您遇到错误,请找到导致错误的代码并在其他地方发布另一个问题。
    【解决方案4】:

    您不能将 IO 字符串转换为字符串。

    然而,你可以做的是将 IO 字符串的内容绑定到一个“变量”,但这仍然会导致整个计算嵌入到 IO 中。

    foo = do
       x <- baz -- here baz is the IO String
       let x' = doStuff x
       return x' -- embeds the String inside IO, as otherwise the computation would result in IO ()
    

    回答你的问题

    foo x = baz x -- x here is your 'IO String'
      where
        baz x = do
          x' <- x
          return $ doStuff x'
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-06-15
      • 2014-09-08
      • 2010-12-07
      • 2013-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多