将monad用于简单的Haskell解析器

时间:2019-04-05 01:11:26

标签: parsing haskell monads

TL; DR

我试图了解这是怎么回事:

satisfy :: (Char -> Bool) -> Parser Char
satisfy pred = PsrOf p
  where
    p (c:cs) | pred c = Just (cs, c)
    p _ = Nothing

等效于此:

satisfy :: (Char -> Bool) -> Parser Char
satisfy pred = do
    c <- anyChar
    if pred c then return c else empty

上下文

这是有关Haskell解析的一些讲义的摘录,我正试图理解:

import Control.Applicative
import Data.Char
import Data.Functor
import Data.List

newtype Parser a = PsrOf (String -> Maybe (String, a))
    -- Function from input string to:
    --
    -- * Nothing, if failure (syntax error);
    -- * Just (unconsumed input, answer), if success.

dePsr :: Parser a -> String -> Maybe (String, a)
dePsr (PsrOf p) = p

-- Monadic Parsing in Haskell uses [] instead of Maybe to support ambiguous
-- grammars and multiple answers.

-- | Use a parser on an input string.
runParser :: Parser a -> String -> Maybe a
runParser (PsrOf p) inp = case p inp of
                            Nothing -> Nothing
                            Just (_, a) -> Just a
                          -- OR: fmap (\(_,a) -> a) (p inp)

-- | Read a character and return. Failure if input is empty.
anyChar :: Parser Char
anyChar = PsrOf p
  where
    p "" = Nothing
    p (c:cs) = Just (cs, c)

-- | Read a character and check against the given character.
char :: Char -> Parser Char
-- char wanted = PsrOf p
--   where
--     p (c:cs) | c == wanted = Just (cs, c)
--     p _ = Nothing
char wanted = satisfy (\c -> c == wanted)   -- (== wanted)

-- | Read a character and check against the given predicate.
satisfy :: (Char -> Bool) -> Parser Char
satisfy pred = PsrOf p
  where
    p (c:cs) | pred c = Just (cs, c)
    p _ = Nothing
-- Could also be:
-- satisfy pred = do
--     c <- anyChar
--     if pred c then return c else empty

instance Monad Parser where
    -- return :: a -> Parser a
    return = pure

    -- (>>=) :: Parser a -> (a -> Parser b) -> Parser b
    PsrOf p1 >>= k = PsrOf q
      where
        q inp = case p1 inp of
                  Nothing -> Nothing
                  Just (rest, a) -> dePsr (k a) rest

我了解直到Monad定义的最后一点的所有内容,特别是我不理解下面的行如何返回Parser b定义所需的(>>=)类型的内容:

Just (rest, a) -> dePsr (k a) rest

如果没有示例,我很难理解Monad的定义。值得庆幸的是,我们在satisfy函数的替代版本中有了一个,它使用了do表示法(这当然意味着正在调用Monad)。我真的还不知道do-notation,所以这里是satisfy的简化版本:

satisfy pred = do
    anyChar >>= (c ->
    if pred c then return c else empty)

因此,根据我们的(>>=)定义的第一行,即

PsrOf p1 >>= k = PsrOf q

我们以anyCharPsrOf p1,以(c -> if pred c then return c else empty)k。我没有得到的是dePsr (k a) rest(k a)中如何返回Parser(至少它是固定的,否则调用dePsr毫无意义)。 rest的存在使情况更加混乱。即使(k a)返回了Parser,调用dePsr也会从返回的Parser中提取基础函数,并将rest作为输入传递给它。绝对不会返回Parser b的定义所要求的(>>=)类型的内容。显然我误解了某个地方。

1 个答案:

答案 0 :(得分:3)

好的,也许这会有所帮助。让我们首先将一些要点放回dePsr中。

dePsr :: Parser a -> String -> Maybe (String, a)
dePsr (PsrOf p) rest = p rest

让我们也写出return :(注意,为了清楚起见,我将所有观点都放在这里)

return :: a -> Parser a
return a = PsrOf (\rest -> Just (rest, a))

现在从Just定义的(>>=)分支开始

 Just (rest, a) -> dePsr (k a) rest

让我们确保我们对每件事物都表示同意:

  • rest应用了p1后未解析的字符串
  • a应用p1
  • 的结果
  • k :: a -> Parser b获取前一个解析器的结果并创建一个新的解析器
  • dePsrParser a退回到函数`String-> Maybe(String,a)

请记住,我们会将 back 再次包装到函数顶部的解析器中:PsrOf q

因此,在英语中,绑定(>>=)a中使用一个解析器,在a中使用一个从b到函式的函数,并在b中返回一个解析器。通过将q :: String -> Maybe (String, b)包装在解析器构造函数PsrOf中来生成结果解析器。然后,组合分析器q取一个名为String的{​​{1}}并将从模式匹配中获得的函数inp应用于第一个解析器,并对结果进行模式匹配。对于错误,我们传播p1 :: String -> Maybe (String,a)(很容易)。如果第一个解析器有一个结果,我们必须拖曳多条信息,仍然是未解析的字符串称为Nothing和结果rest。我们将a赋予第二个解析器组合器a,并得到一个k,我们需要将其与Parser b拆开以得到一个函数(dePsr。该功能可以应用于String -> Maybe (String,b),以获得组合解析器的最终结果。

我认为阅读此书最难的部分是有时我们curry解析器函数会掩盖实际发生的事情。

好,rest示例

satisfy

satisfy pred = anyChar >>= (c -> if pred c then return c else empty) 来自备用实例,并且为empty,因此解析器始终失败。

让我们只看成功的分支。通过仅替换成功部分:

PsrOf (const Nothing)

因此在绑定PsrOf (\(c:cs) ->Just (cs, c)) >>= (\c -> PsrOf (\rest -> Just (rest, c))) 定义中

  • (>>=)
  • p1 = \(c:cs -> Just (cs, c))
  • k = (\c -> PsrOf (\rest -> Just (rest, c)))再次仅成功分支

然后q inp = let Just (rest,a) = p1 inp in dePsr (k a) rest变成

q

进行一些β-还原

q inp = 
  let Just (rest, a) = (\(c:cs) -> Just (cs, c)) inp
   in dePsr (\c -> PsrOf (\rest -> Just (rest, c))) a rest

最后清理更多

q inp =
 let (c:cs) = inp
     rest = cs
     a = c
  in dePsr (PsdOf (\rest -> Just (rest, a))) rest -- dePsr . PsrOf = id

因此,如果q (c:cs) = Just (cs, c) 成功,我们将pred还原为恰好我们期望的satisfy正是我们在第一个示例中找到的这个问题。我将其保留为原样,然后让读者(阅读:我很懒)证明,如果anyCharinp = ""的结果与第一个{{1 }}示例。


注意:如果您正在执行除类分配以外的任何其他操作,则从一开始就通过错误处理开始,使解析器pred c = False易于使错误类型更多,这将为您节省数小时的痛苦和沮丧。一般情况下,但PITA会将所有内容从Nothing更改为satisfy


问题:“ [C]您要解释一下将“点”放回去后如何从String -> Either String (String,a)到达Maybe吗?

答案:首先,很不幸,给出Monad实例定义而没有Functor和Applicative定义。 Eitherreturn a = PsrOf (\rest -> Just (rest, a))函数必须相同(这是Monad法则的一部分),并且它们将被称为同一事物,只是return = pure在Haskell历史上很早就适用。实际上,我不“知道”纯净的外观,但我知道它的含义,因为它是唯一可能的定义。 (如果您想了解该陈述的证明,我已经阅读了论文,并且知道了结果,但是我对输入Lambda演算的了解不足,不足以对再现结果有信心。)

pure必须在上下文中包装一个值,而不能更改上下文。

return

Monad是一个函数,该函数接受要解析的字符串并返回return值以及原始字符串的任何未解析部分或返回return :: Monad m => a -> m a return :: a -> Parser a -- for our Monad return :: a -> PsrOf(\str -> Maybe (rest, value)) -- substituting the constructor (PSUDO CODE) PsrOf Parser `。解析器总是成功,因此我们必须返回Just值。

Just

Nothing on failure, all wrapped in the constructor是上下文,它未经更改地传递。 . The context is the string to be parsed, so we cannot change that. The value is of course what was passed to是我们放在Monad上下文中的值。

出于完整性考虑,这也是Functor对return a = PsrOf (\rest -> Just (rest, a)) 的唯一合理定义。

rest