理解做符号和绑定

understanding do notation and bindings

我是 haskell 的新手,我正在尝试了解本文档中用于创建 Monadic 解析器的方法 https://www.cs.nott.ac.uk/~gmh/pearl.pdf

我没有完全遵循它,而是尝试做一些不同的事情以便正确理解它,因此,我最终得到了这段代码

newtype Parser a = Parser (String -> Maybe (a, String))

item :: Parser Char
item = Parser (\cs -> case cs of
            "" -> Nothing
            (c:cs) -> Just (c, cs))

getParser (Parser x) = x

instance Monad Parser where
    return x = Parser (\cs -> Just (x,cs))
    (Parser p) >>= f  = Parser (\cs -> let result = p cs in
                  case result of
                    Nothing -> Nothing
                    Just (c,cs') -> getParser (f c) cs')

takeThreeDropSecond :: Parser (Char, Char)
takeThreeDropSecond = do
    c1 <- item
    item
    c2 <- item
    return (c1, c2)

这似乎有效,但我很难理解 do 表示法中发生的事情。

例如;在 c1 <- item 中,分配给 c1 的是什么?它是 Parser 类型中包含的函数,还是该计算的结果,还是其他什么?此外,do 符号中的第二行只是 item,那么它只是 运行 item 但不分配结果吗?最后,return (c1,c2) 会产生什么?是 Parser (String -> Maybe ((c1, c2)), String) 还是只是 Just (c1, c2)

Parser 类型包含一个函数,该函数可以 1) 使用 Maybe 表示失败和 2) returns 未通过 (a, String) 解析的剩余文本3) 一些被解析的值 a,可以是任何值。 monad 实例是将它们连接在一起的管道。 return 实现围绕一个函数创建了一个 Parser,该函数 1) 以 Just 成功,2) 不修改其输入文本,以及 3) 直接传递给它的值。 >>= 实现采用一个解析器和一个函数,然后 returns 一个由第一个 运行 p 创建的新解析器,然后根据该结果是通过还是失败 运行 f.

takeThreeDropSecond中,第一个c1 <- item说"parse the given using item, assign its result to c1, and feed the rest of the input forward"。这不会将 item 解析器内部的函数分配给 c1,它会将 运行 内部函数 item 的结果分配给当前输入。然后你到达 item,它使用 item 解析一个值,不将它分配给任何东西,并将输入的其余部分向前馈送。接下来到达 c2 <- item,它的作用与第一行基本相同,最后是 return (c1, c2),它将扩展为 Parser (\cs -> Just ((c1, c2), cs))。这意味着 return (c1, c2) 的类型为 Parser (Char, Char)。使用类型注释,它将是

takeThreeDropSecond :: Parser (Char, Char)
takeThreeDropSecond = do
    (c1 :: Char) <- (item :: Parser Char)
    (item :: Parser Char)
    (c2 :: Char) <- (item :: Parser Char)
    (return (c1, c2) :: Parser (Char, Char))

请注意,任何 monadic do 块的最后一行必须与其所属的函数具有相同的类型。因为 return (c1, c2) 有类型 Parser (Char, Char),所以必须 takeThreeDropSecond,反之亦然。