Haskell解析器组合 - 做符号

时间:2016-08-16 23:30:54

标签: haskell parser-combinators do-notation

我正在阅读有关构建解析器组合库的教程,我遇到了一个我不太了解的方法。

newtype Parser a = Parser {parse :: String -> [(a,String)]}

chainl :: Parser a -> Parser (a -> a -> a) -> a -> Parser a
chainl p op a = (p `chainl1` op) <|> return a

chainl1 :: Parser a -> Parser (a -> a -> a) -> Parser a
p `chainl1` op = do {a <- p; rest a}
  where rest a = (do f <- op
                     b <- p
                     rest (f a b))
                 <|> return a

bind :: Parser a -> (a -> Parser b) -> Parser b
bind p f = Parser $ \s -> concatMap (\(a, s') -> parse (f a) s') $ parse p s

bind(>>=)运算符的实现。我不太了解chainl1函数的工作原理。从我可以看到你从f中提取op,然后你将它应用到f a b然后你递归,但我不知道你应该如何从解析器中提取一个函数元组列表?

1 个答案:

答案 0 :(得分:1)

首先查看Parser

的定义
newtype Parser a = Parser {parse :: String -> [(a,String)]}`

Parser a实际上只是一个函数的包装器(我们可以稍后用parse运行),它接受String并返回一对对列表,其中每对包含一个处理字符串时遇到a,以及剩余的要处理的字符串。

现在查看chainl1中令您困惑的部分代码:从f中提取op的部分:

f <- op

您评论道:“我不知道如何在解析器返回元组列表时从解析器中提取函数。”

当我们运行一个带有字符串的Parser a(使用parse)时,我们会得到一个类型为[(a,String)]的列表。但是这段代码没有说parse op s。相反,我们在这里使用bind(使用do-notation语法糖)。问题在于您正在考虑Parser数据类型的定义,但您并没有考虑bind具体做什么。

让我们更仔细地看看bind monad中Parser正在做什么。

bind :: Parser a -> (a -> Parser b) -> Parser b
bind p f = Parser $ \s -> concatMap (\(a, s') -> parse (f a) s') $ parse p s

p >>= f做什么?它返回Parser,当给定字符串s时,执行以下操作:首先,运行解析器p,其中包含要解析的字符串s 1}}。正如您所正确指出的,这会返回类型[(a, String)]的列表:即遇到类型a的值列表,以及遇到每个值后剩余的字符串。然后它采用这个对列表并将函数应用于每对。具体来说,此列表中的每个(a, s')对都通过以下方式进行转换:(1)将f应用于解析后的值af a返回新的解析器),然后(2) 使用剩余的字符串s'运行这个新的解析器。这是一个从元组到元组列表的函数:(a, s') -> [(b, s'')] ...因为我们将这个函数映射到parse p s返回的原始列表中的每个元组,这最终给了我们一个元组列表列表:[[(b, s'')]]。因此,我们将此列表连接(或加入)到单个列表[(b, s'')]中。总而言之,我们有一个从s[(b, s'')]的函数,然后我们将其换成Parser个新类型。

关键是,当我们说f <- opop >>= \f -> ...将名称f分配给由op解析的值时,f是不是元组列表,b / c它不是运行parse op s的结果。

通常,您会看到很多Haskell代码定义了一些数据类型SomeMonad a,以及一个bind方法,它为您隐藏了大量脏信息,并允许您访问您所关心的a值使用了这样的符号:a <- ma。查看State a monad以了解bind如何在幕后为你传递状态可能是有益的。类似地,在这里,当组合解析器时,您最关心解析器应该识别的值... bind隐藏所有涉及在识别类型{{1}的值时保留的字符串的脏工作}。