Parsec --- 2.3 Predictive parsers
Parsecの選択 <|> は「predictive」だとのこと.predictive,予言的.最初のパーサが入力を一切消費しないときのみ,次のパーサに処理が進むということらしいです.
testOr::Parser String testOr = string "(a)" <|> string "(b)"
というのを作ってみると
*Main> parseTest testOr "(a)" "(a)" *Main> parseTest testOr "(b)" parse error at (line 1, column 1): unexpected "b" expecting "(a)"
なるほど,(a)はOKでも,(b)は ( が消費されるので,string "(a)"の方にいってしまって,sting "(b)"には行かずにエラーとなるわけですか.Parsecのドキュメントによると,これを解決するにはleft-factor(「左分解」?),つまり接頭辞をくくりだせということです.こうしてみろとあります.
testOr1::Parser Char testOr1 = do char '(' char 'a' <|> char 'b' char ')'
実行してみます.
*Main> parseTest testOr1 "(a)" ')' *Main> parseTest testOr1 "(b)" ')' *Main>
たしかにパースには成功してますが,testOrとは型が違いますし,返されるものは当然最後にパースされた ) だけですので気分はよくないです.そこでこうしてみます.
testOr1'::Parser String testOr1' = do s1 <- char '(' s2 <- char 'a' <|> char 'b' s3 <- char ')' return [s1,s2,s3]
こうすれば
*Main> parseTest testOr1' "(a)" "(a)" *Main> parseTest testOr1' "(b)" "(b)"
となりすっきりします.ただ・・・s1とかs2,s3とそれぞれのパーサの結果を一次退避的に保存しないといけないのでしょうか.うまく隠蔽できないものかでしょうか.
このような左分解がいつでもできるわけではないし,できたとして複雑になるので,失敗したら最初に戻ってくれる(バックトラックする)tryという関数が用意されています.これを使えば,
testOr2::Parser String testOr2 = try(string "(a)") <|> string "(b)"
とかくことで,
*Main> parseTest testOr2 "(a)" "(a)" *Main> parseTest testOr2 "(b)" "(b)"
とできます.だいぶすっきりします.ドキュメントによるとこれよりも
testOr3::Parser String testOr3 = do try $ string "(a" >> char ')' return "(a)" <|> string "(b)"
の方が``even better''だそうです.確かにそうなんでしょうが,可読性という点では前の方がずっとよいと思います.