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''だそうです.確かにそうなんでしょうが,可読性という点では前の方がずっとよいと思います.