Parsing 一元语法分析-将多个语法分析器粘合在一起
我正在阅读功能性pearl论文(在Parsing 一元语法分析-将多个语法分析器粘合在一起,parsing,haskell,Parsing,Haskell,我正在阅读功能性pearl论文(在haskellforall.com上推荐阅读该论文以理解解析之后)。在第3页第4节之前,我编写了一个实现,如下所示: newtype Parser a = Parser (String -> [(a,String)]) parse (Parser p) = p instance Monad Parser where return a = Parser (\cs -> [(a,cs)]) p >>= f = Parser (\
haskellforall.com
上推荐阅读该论文以理解解析之后)。在第3页第4节之前,我编写了一个实现,如下所示:
newtype Parser a = Parser (String -> [(a,String)])
parse (Parser p) = p
instance Monad Parser where
return a = Parser (\cs -> [(a,cs)])
p >>= f = Parser (\cs -> concat [parse (f a) cs' | (a,cs') <- parse p cs])
item :: Parser Char
item = Parser (\cs -> case cs of
"" -> []
(c:cs) -> [(c,cs)])
p :: Parser (Char,Char)
p = do { a <- item; item; b <- item; return (a,b)}
语法
对于>
,code>并不总是语法上的甜点
相反,我们有:
do m ; n = m >> n
do x<-m ; n = m >>= \x -> n
在这里,您可以看到第一个和第三个项
没有丢弃它们的结果(因为>=
分别将它们绑定到a
和b
),而中间的项
还要注意的是,代码
\cs -> case cs of
"" -> []
(c:cs) -> [(c,cs)]
因为它两次定义变量cs
:一次在\cs
中,一次在
图案(c:cs)
。相当于
\cs -> case cs of
"" -> []
(x:xs) -> [(x,xs)]
这澄清了最后的字符串
是输出,而不是原始的cs
字符串,而是它的尾部xs
在一篇评论中,发帖人想知道为什么item
的三种用法不返回相同的结果,即为什么在中返回(a,b)
字符a
不等于b
。这是由于>=
一元运算符造成的,在该解析器中
monad自动将每个项的输出字符串xs
输入到下一个项。事实上,这个monad的全部目的是帮助将每个解析器的“剩余”输出作为下一个解析器的“待消费”输入提供。这有两个优点:它使程序员无需编写代码来传递该字符串,并确保该字符串不会意外地“重绕”到以前的状态。为了说明后一点,这里有一些错误的代码:
let [(c1,s1)] = someParser someInitialString
[(c2,s2)] = anotherParser1 s1
[(c3,s3)] = anotherParser2 s2
[(c4,s4)] = anotherParser3 s3
[(c5,s5)] = anotherParser4 s2 -- Whoops! Should have been s4
in [c1,c2,c3,c4,c5]
在最后一步中,字符串在被多次使用之后,被错误地回滚到以前的状态,就好像解析器anotherParser2
和anotherParser3
根本没有使用任何东西一样。通过使用>=
组合解析器可以防止此错误。我将尝试对>
进行更多说明。
正如您在另一个答案中所看到的,您应该将do分解为>=
,以便更好地了解发生了什么
例如,让我们编写一个解析器,解析两个字符并返回它们
twoChars :: Parser (Char,Char)
twoChars = do
i <- item
j <- item
return (i,j)
为了清楚起见,我用括号括起来。如您所见,第二个项
在匿名函数中接收第一个项
解析器的结果,结果绑定到i
。>=
函数接受一个解析器,一个函数,然后返回一个解析器。理解它的最佳方法是将其插入定义中:
f = \i → item »= \j → return (i,j)
twoChars = item >>= f
twoChars = Parser (\cs -> concat [parse (f a) cs' | (a,cs') <- parse item cs])
这个解析器将被传递剩下的字符串以进行处理(“bc”
),当上面的\j
绑定到b
时,它将使用项解析器来取出b
。然后我们得到一个return('a','b')
语句,它将('a','b')
放入一个只返回('a','b')
的解析器中
我希望这澄清了信息流是如何发生的。现在,假设您想忽略一个角色。你可以这样做
twoChars :: Parser (Char,Char)
twoChars =
item >>= \i ->
item >>= \j ->
item >>= \k ->
return (i,k)
可以将j
绑定到'b'
对于示例“abc”,您永远不会使用它。我们可以用\ucode>代替j
twoChars :: Parser (Char,Char)
twoChars =
item >>= \i ->
item >>= \_ ->
item >>= \k ->
return (i,k)
但是我们也知道,>::ma->mb->mb
可以定义为:
p >> q = p >>= \_ -> q
所以我们只剩下
twoChars :: Parser (Char,Char)
twoChars =
item >>= \i ->
item >>
item >>= \k ->
return (i,k)
最后,您可以将其添加回do
。>
的应用程序只是将糖分添加到一个没有边界的单行语句中。其结果是:
twoChars :: Parser (Char,Char)
twoChars = do
i <- item
item
j <- item
return (i,j)
twoChars::解析器(Char,Char)
twoChars=do
我你的翻译更统一
p3 = do { a <- item; item; b <- item; return (a,b)}
-- do { a <- item; z <- item; b <- item; return (a,b)} -- z is ignored
(这里的关键观察是函数是嵌套的)。也就是说
-- parse (return a) cs = [(a,cs)]
-- parse (p >>= f) cs = [r | (a,cs1) <- parse p cs, -- concat
-- r <- parse (f a) cs1] ) -- inlined !
parse p3 cs
= [ r | (a,cs1) <- parse item cs,
r <- [ r | (z,cs2) <- parse item cs1,
r <- [ r | (b,cs3) <- parse item cs2,
r <- -- parse (return (a,b)) cs3
[((a,b),cs3)]]]] -- z is unused
= [ ((a,b),cs3) | (a,cs1) <- parse item cs,
(_,cs2) <- parse item cs1,
(b,cs3) <- parse item cs2]
. :) -- 对于您的第二个问题,大小写
绑定隐藏了lambda绑定。您的代码完全相同。-首先,do
chains定义嵌套的绑定定义,因此无需“传递”任何内容-外部绑定仅在最内层可用(如下面答案中的翻译代码所示)。@WillNess,我忘记了do表示法的这一方面!我现在会更清楚地记得这一点,因为这一次,它真的难住了我:)我认为关于第二部分,应该是cs的。它确实是这样工作的,但我的解释是,模式匹配阴影的lambda是相当混乱的。@半径,你的解释似乎是正确的。我用一个函数做了一个快速检查,并进行了验证。这就消除了关于cs
部分的混淆。这有助于消除一些混淆。我仍然不清楚字符串在传递到后续的项之前是如何删除的。例如,第三个项
在一元包装器中仍应具有与第一个项
相同的功能。因此,执行它仍然应该得到与第一个项相同的结果,因为输入字符串没有改变-因此a
和b
应该是相同的。显然不是这样。你应该更新你的答案,同时提到case
中item
的cs
语句隐藏了lambda的cs
,然后你对他的问题有了完整的答案。回答得好!你对>=
的解释起到了拼凑缺失片段的作用。我怀疑大多数刚接触解析基础知识的人都会在这里结结巴巴。希望当有人写了一篇来自functional pearl paper的哈斯克尔学院的文章时,他们能用你的答案来展开讨论。
twoChars :: Parser (Char,Char)
twoChars =
item >>= \i ->
item >>
item >>= \k ->
return (i,k)
twoChars :: Parser (Char,Char)
twoChars = do
i <- item
item
j <- item
return (i,j)
p3 = do { a <- item; item; b <- item; return (a,b)}
-- do { a <- item; z <- item; b <- item; return (a,b)} -- z is ignored
p3 = item >>= (\a ->
item >>= (\z ->
item >>= (\b ->
return (a,b)))) -- z is unused
-- parse (return a) cs = [(a,cs)]
-- parse (p >>= f) cs = [r | (a,cs1) <- parse p cs, -- concat
-- r <- parse (f a) cs1] ) -- inlined !
parse p3 cs
= [ r | (a,cs1) <- parse item cs,
r <- [ r | (z,cs2) <- parse item cs1,
r <- [ r | (b,cs3) <- parse item cs2,
r <- -- parse (return (a,b)) cs3
[((a,b),cs3)]]]] -- z is unused
= [ ((a,b),cs3) | (a,cs1) <- parse item cs,
(_,cs2) <- parse item cs1,
(b,cs3) <- parse item cs2]
parse p3 cs =
for each (a,cs1) in (parse item cs):
for each (z,cs2) in (parse item cs1):
for each (b,cs3) in (parse item cs2):
yield ((a,b),cs3)