[英]Parsing Haskell custom data types
我已經通過這里提供的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
來傳遞斷言?
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
原子怎么樣? 為了傳遞測試用例,似乎原子必須是1對多的字母數字字符或反斜杠。 同樣,這個不相交的結構可以立即在我們的解析器中表達
sym = many1 (_ <|> _)
我們將再次使用一些內置的簡單解析器組合來構建我們想要的東西,比如satisfy :: (Char -> Bool) -> Parser Char
匹配任何滿足某些謂詞的字符。 我們可以立即構建另一個有用的組合器, char c = satisfy (==c) :: Char -> Parser Char
然后我們就完成了。
sym = many1 (char '/' <|> satisfy isAlpha)
其中isAlpha
是一個謂詞,就像正則表達式[a-zA-Z]
。
所以現在我們有了解析器的核心
natural <|> sym :: Parser String
many1
組合器將我們的字符解析器提升為字符列表的解析器( String
s!)。 這個提升動作也是構建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
每一個都說明了將String
轉換為Atom
的方法,同時也聲明了我們正在處理的是什么類型的Atom
。 值得注意的是,如果我們傳遞一個無法解析為Int
的字符串, liftInt
將拋出運行時錯誤。 幸運的是,這正是我們所知道的。
atomInt :: Parser Atom
atomInt = liftInt <$> natural
atomSym :: Parser Sym
atomSym = liftSym <$> sym
atom'' = atomInt <|> atomSym
現在我們的atom''
解析器利用了natural
只返回有效解析的字符串的保證 - 我們對read
的調用不會失敗!---我們嘗試按順序構建AInt
和ASym
,像ADT的結構一樣,在一個不相交的結構中一個接一個地嘗試。
整個社會就是這樣
atom''' = AInt . read <$> many1 digit
<|> ASym <$> many1 ( char '/'
<|> satisfy isAlpha)
這展示了解析器組合器的樂趣。 整個事物是使用小巧,可組合的簡單部件從地面構建的。 每個人都做了一個非常小的工作,但他們一起跨越了大量的解析器。
您還可以使用ADT中的更多分支,更完整指定的符號類型解析器或使用<?>
故障裝飾輕松擴充此語法,以便在失敗的分析中包含大量錯誤消息。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.