2

我已经完成了这里提供的 Haskell Koans: https ://github.com/roman/HaskellKoans

我被困在最后两个 Koans 上,都涉及解析自定义代数数据类型。这是第一个:

data Atom = AInt Int | ASym Text deriving (Eq, Show)

testAtomParser :: Test
testAtomParser = testCase "atom parser" $ do
    -- Change parser with the correct parser to use
    --
    let parser = <PARSER HERE> :: P.Parser Atom
    assertParse (ASym "ab") $ P.parseOnly parser "ab"
    assertParse (ASym "a/b") $ P.parseOnly parser "a/b"
    assertParse (ASym "a/b") $ P.parseOnly parser "a/b c"
    assertParse (AInt 54321) $ P.parseOnly parser "54321"

如何定义变量解析器以便它可以解析代数数据类型Atom以传递断言?

4

1 回答 1

9

一世。

ADT 的解析器倾向于反映 ADT 的形状。您的 ADT 由两个不相交的部分组成,因此您的解析器可能也有两个不相交的部分

atom = _ <|> _

二、

假设我们知道如何解析单个数字(让我们称之为基本解析器digit)然后我们通过重复它来解析一个(非负)整数。

natural = let loop = digit >> loop in loop

这成功地解析了无限的数字流并将它们丢弃。我们能做得更好吗?不幸的是,不仅仅是一个 monad 实例,我们还需要另一个基本组合many器 ,它修改一些其他解析器以消耗输入 0 次或更多次,将结果累积到一个列表中。我们实际上会稍微调整一下,因为空解析不是有效数字

many1 p = do x  <- p
             xs <- many p
             return (x:xs)

natural' = many1 digit

三、

原子呢?要通过测试用例,似乎原子必须是一对多的字母数字字符反斜杠。同样,这种不相交的结构可以立即在我们的解析器中表达

sym = many1 (_ <|> _)

我们将再次使用一些内置的简单解析器组合器来构建我们想要的东西,比如satisfy :: (Char -> Bool) -> Parser Char匹配任何满足某个谓词的字符。我们可以立即构建另一个有用的组合器,char c = satisfy (==c) :: Char -> Parser Char然后我们就完成了。

sym = many1 (char '/' <|> satisfy isAlpha)

where isAlphais a predicate 很像 regex [a-zA-Z]

四。

所以现在我们有了解析器的核心

natural <|> sym :: Parser String

many1组合器将我们的字符解析器提升为字符列表( Strings!) 的解析器。这种提升动作也是构建 ADT 解析器的基本思想。我们想把我们Parser String提升到Parser Atom. 一种方法是使用一个函数toAtom :: String -> Atom,然后我们可以将其fmap放入Parser

atom' :: Parser Atom
atom' = fmap toAtom (natural <|> sym)

但是具有类型的函数String -> Atom首先破坏了构建解析器的目的。

如 I. 中所述,重要的部分是 ADT 的形状反映在我们的atom解析器的形状中。我们需要利用它来构建我们的最终解析器。

五。

我们需要利用atom解析器结构中的信息。让我们构建两个函数

liftInt :: String -> Atom  -- creates `AInt`s
liftSym :: String -> Atom  -- creates `ASym`s

liftInt = AInt . read
liftSym = ASym

每个都说明了将Strings 转换为Atoms 的方法,还说明了我们正在处理的类型。Atom值得注意的是,liftInt如果我们将一个无法解析为Int. 幸运的是,这正是我们所知道的。

atomInt :: Parser Atom
atomInt = liftInt <$> natural

atomSym :: Parser Sym
atomSym = liftSym <$> sym

atom'' = atomInt <|> atomSym

现在,我们的atom''解析器利用了natural只返回对自然有效解析的字符串的保证——我们的调用read不会失败!——我们尝试按顺序构建两者AIntASym在不相交的情况下一个接一个地尝试结构就像我们的 ADT 的结构一样。

六、

整个shebang是这样的

atom''' =     AInt . read <$> many1 digit
          <|> ASym <$> many1 (    char '/' 
                              <|> satisfy isAlpha)

这显示了解析器组合器的乐趣。整个东西是用微小的、可组合的、简单的部件从地面上建立起来的。每个人都做了一个非常小的工作,但它们一起跨越了很大的解析器空间。

您还可以轻松地在 ADT 中使用更多分支、更彻底指定的符号类型解析器或失败修饰来扩充此语法,<?>以便在解析失败时获得大量错误消息。

于 2013-02-01T22:40:51.057 回答