【问题标题】:How does p >>= f works in Parser instance?p >>= f 如何在 Parser 实例中工作?
【发布时间】:2018-08-23 14:02:40
【问题描述】:
class Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b

instance Monad Parser where
    return a = Parser (\cs -> [(a,cs)])
    p >>= f = Parser (\cs -> concat [parse (f a) cs’ | (a,cs’) <- parse p cs])

paper上有解释

(>>=) 运算符是解析器的排序运算符。用一个 由 parse (Parser p) = p 定义的解析器的解构函数, 解析器 p >>= f 首先将解析器 p 应用于参数字符串 cs 给出形式为 (a,cs') 的结果列表,其中 a 是一个值 cs' 是一个字符串。对于每个这样的对,f a 是一个解析器,它是 应用于字符串 cs'。结果是一个列表列表,即 然后连接起来给出最终的结果列表。

但这仍然不清楚。谁能用一些例子来解释它?非常感谢。

【问题讨论】:

  • 也许在研究&gt;&gt;= 运算符本身之前,您是否理解解析器包装ByteString -&gt; [(a, ByteString)] 类型函数背后的想法?
  • &gt;&gt;= 的想法是启用详尽搜索将字符串解析为对象的有效方法。它与列表[]&gt;&gt;= 有点相关。
  • 什么不清楚?
  • @Willem Van Onsem 是的,论文上提到了
  • @Cubic 就像 p>>f 如何首先将解析器 p 应用于参数字符串 cs?它会跳过小节 | 之前的所有内容。

标签: parsing haskell


【解决方案1】:

在 Haskell 中,解析器是 Monad 的一个示例。 Monad 实际上是一个比解析器更广泛的主题,但它们实际上很好地介绍了这个概念。这里是它的工作原理。

在解析器中,您希望能够识别(在 BNF 中)之类的规则

assignment ::= identifier "=" expression

在 Haskell 中你可以这样写:

data Statement = 
    Assignment Identifier Expression
    | Block [Statement]  -- A block is a list of statements.
    | Conditional Expression Statement Statement
    | -- etc. 

identifier :: Parser Identifier  -- Implementation omitted.

literal :: String -> Parser ()   -- Implementation omitted.

expression :: Parser Expression   -- Implementation omitted.

assignment :: Parser Statement
assignment = do          -- Note the "do" here.
   ident <- identifier   
   literal "="          
   expr <- expression
   return (Assignment ident expr)   -- "return" doesn't mean what you think.

这体现了赋值解析器是一系列子解析器的想法:首先解析标识符,然后识别“=”,然后解析右侧的表达式。希望您能看到 BNF 如何映射到 Haskell 代码中。

Haskell 中的“do”语法是使用&gt;&gt;= 运算符(称为“bind”)的表达式的语法糖。上面的 assignment 示例将其转化为以下内容(大约:我在这里跳过了有关模式匹配失败的内容)

assignment = 
    identifier >>= (\ident ->
        operator "=" >>= (\dummy ->
           expression >>= (\expression -> return (Assignment ident expr))))

每个\(称为“lambda”)都引入了一个匿名函数。 &gt;&gt;= 运算符有两个参数。左侧是 Parser monad 中包含的一些值。右边是一个函数,它接受这个值并返回一个新的包装值。绑定运算符的工作是解开左边的值(这可能涉及一些神奇的副作用)并将其传递给右边的函数。在这种情况下,神奇的副作用包括消耗输入文本。

请注意绑定和匿名函数是如何嵌套在脱糖版本中的。 “do”语法中的每一行都转换为前一行的一个新函数。这意味着最后一个函数可以访问到目前为止所有函数中的所有变量。它是一种用 Haskell 等纯函数式语言对一组赋值进行建模的方法。

我在 cmets 中说过,“返回”并不意味着您认为它意味着什么。在 Haskell 中,它与控制流无关,它只是将一个值包装在一个 monad 中(在本例中为 Parser),本身不会产生任何副作用。

这个特定的解析器会生成结果列表。也就是说,不是在每一步都决定 One True Parse,而是在遇到模棱两可的东西时生成一个结果列表。到目前为止,绑定运算符获取每个结果并将其传递给其右侧的函数,该函数又可能再次生成多个结果。或者解析器可能不会产生任何结果,在这种情况下该分支被放弃。因此,每一步都从一个结果列表开始,下一步给出一个结果列表,然后通过“concat”函数将其折叠回一个平面结果列表。

您问题中给出的Monad 类是标准库的一部分。碰巧有很多有用的函数适用于所有 monad,所以给它们一个通用接口效果很好。对于Parser,绑定运算符的类型为

(>>=) :: Parser a -> (a -> Parser b) -> Parser b

并且return 具有类型

return :: a -> Parser a

冥想这些类型以及我对 bind 和 return 所做的描述,你可能会获得一元的启蒙。

【讨论】:

    猜你喜欢
    • 2015-08-20
    • 1970-01-01
    • 1970-01-01
    • 2020-01-22
    • 1970-01-01
    • 2018-09-15
    • 2011-04-27
    • 1970-01-01
    • 2014-07-15
    相关资源
    最近更新 更多