残念ながら、型レベルのトリッキーなしでは、これを完全に一般的に行うことはできません。ただし、Daniel Wagner の提案に沿ったアプローチは、ポリモーフィック コンビネータといくつかの既存の再帰インスタンスを使用して、DIY スタイルで構築できます。簡単な例を示して説明します。
最初に、いくつかの単純なパーサーでテストします。
type Parser = Parsec String ()
parseNumber :: Parser Int
parseNumber = read <$> many1 digit
parseBool :: Parser Bool
parseBool = string "yes" *> return True
<|> string "no" *> return False
parseName :: Parser String
parseName = many1 letter
次に、可能性の終わりを示す「基本ケース」型を作成し、入力なしで常に成功するパーサーを与えます。
data Nil = Nil deriving (Eq, Ord, Read, Show, Enum, Bounded)
instance Monoid Nil where
mempty = Nil
mappend _ _ = Nil
parseNil :: Parser Nil
parseNil = return Nil
もちろん、これは と同等です。()
実際に を解析したい場合に備えて、あいまいさを解消するために新しい型を作成しているだけです()
。次に、パーサーを連結するコンビネーター:
infixr 3 ?|>
(?|>) :: (Monoid b) => Parser a -> Parser b -> Parser ([a], b)
p1 ?|> p2 = ((,) <$> ((:[]) <$> p1) <*> pure mempty)
<|> ((,) <$> pure [] <*> p2)
ここで何が起こっているかというと、 p1 ?|> p2
tryp1
が成功した場合、それをシングルトン リストにラップしmempty
、タプルの 2 番目の要素を埋めます。失敗した場合p1
は、空のリストを埋めp2
て 2 番目の要素を使用します。
parseOne :: Parser ([Int], ([Bool], ([String], Nil)))
parseOne = parseNumber ?|> parseBool ?|> parseName ?|> parseNil
パーサーと新しいコンビネーターの組み合わせは簡単で、結果の型は一目瞭然です。
parseMulti :: Parser ([Int], ([Bool], ([String], Nil)))
parseMulti = mconcat <$> many1 (parseOne <* newline)
Monoid
複数の行を結合するために、タプルの再帰インスタンスとリストの通常のインスタンスに依存しています。ここで、それが機能することを示すために、簡単なテストを定義します。
runTest = parseTest parseMulti testInput
testInput = unlines [ "yes", "123", "acdf", "8", "no", "qq" ]
として正常に解析されRight ([123,8],([True,False],(["acdf","qq"],Nil)))
ます。