我正在阅读有关构建解析器组合库的教程,我遇到了一个我不太了解的方法。
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
然后你递归,但我不知道你应该如何从解析器中提取一个函数元组列表?
答案 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
应用于解析后的值a
(f a
返回新的解析器),然后(2) 使用剩余的字符串s'
运行这个新的解析器。这是一个从元组到元组列表的函数:(a, s') -> [(b, s'')]
...因为我们将这个函数映射到parse p s
返回的原始列表中的每个元组,这最终给了我们一个元组列表列表:[[(b, s'')]]
。因此,我们将此列表连接(或加入)到单个列表[(b, s'')]
中。总而言之,我们有一个从s
到[(b, s'')]
的函数,然后我们将其换成Parser
个新类型。
关键是,当我们说f <- op
或op >>= \f -> ...
将名称f
分配给由op
解析的值时,f
是不是元组列表,b / c它不是运行parse op s
的结果。
通常,您会看到很多Haskell代码定义了一些数据类型SomeMonad a
,以及一个bind
方法,它为您隐藏了大量脏信息,并允许您访问您所关心的a
值使用了这样的符号:a <- ma
。查看State a
monad以了解bind
如何在幕后为你传递状态可能是有益的。类似地,在这里,当组合解析器时,您最关心解析器应该识别的值... bind
隐藏所有涉及在识别类型{{1}的值时保留的字符串的脏工作}。