Parsing 一元语法分析-将多个语法分析器粘合在一起

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 (\

我正在阅读功能性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 (\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)