在 where 子句下做语句

Do statement under a where clause

我正在尝试使用 <- 绑定将 IO [String] 转换为 [String];但是,我需要使用 do 块在 where 语句下执行此操作,但 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 块?而且,我们有没有机会在看守面前做一些陈述?

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

但是,您可以将 IO String 的内容绑定到 'variable',但这仍然会导致整个计算嵌入到 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'

记住:do-blocks 是 syntactic sugar for monadic notation。这意味着以下内容适用:

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

换句话说,当使用do-表示法时,您实际上是在产生值。这就是为什么不能在 where 语句的顶层只包含一个 do 块。

解决这个问题的方法是将函数放入 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 []

要了解 monads、do-notation、>>=>>,我强烈建议阅读 the LYAH chapters 以在尝试更多 monadic 代码之前获得更好的理解.

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

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

Do表示法用来写一般形式的表达式

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
          }

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

do 符号变量被 <- 构造称为 "bound"。它们在引入时进入作用域(在 <- 的左侧)并保留在所有后续 do 子表达式的作用域中。

一个可用于任何 monad 的内置 monadic 表达式是 return :: Monad m => a -> m a。因此 x <- return v x 绑定到 v,这样 x 将在后续的子表达式中可用,并且将具有v.

的值

所有 do 变量都 confined 那个 do 块, 不能在它之外使用。每个变量的范围是同一 do 块中的所有代码,位于变量绑定之后/之后。

这也意味着 <- 是一个非递归绑定,因为变量不能同时出现在它的右侧和左侧:它将是两个 具有相同名称的不同 个变量,在这种情况下,右侧的变量必须在该点之上的某处建立。

这里有一些通用模式:

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 => m a 表达式都只是表达式,因此可以是一个 if - then - else 表达式,其结果分支和替代分支都是相同的单子类型(通常是让初学者感到困惑):

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

更新: monad 的要点之一是其将效果与纯计算完全分离。一旦一个monad中,你就不能"get out"。 Monadic计算可以使用纯计算,反之则不行。