残念ながら、タイプレベルの細かいことがない限り、これを完全に一般的に行うことはできません。しかし、Daniel Wagnerの提案に沿ったアプローチは、多態的なコンビネータといくつかの既存の再帰的インスタンスを使用してDIYスタイルで構築することができます。デモンストレーションするための簡単な例を紹介します。
まず、でテストするには、いくつかのsimplemindedパーサ:
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
p1 ?|> p2
試みていること、そしてそれはシングルトンリスト中のタプルの2番目の要素のためにmempty
に充填するラップを成功した場合です。 p1
が失敗した場合、空のリストが埋められ、2番目の要素にはp2
が使用されます。
parseOne :: Parser ([Int], ([Bool], ([String], Nil)))
parseOne = parseNumber ?|> parseBool ?|> parseName ?|> parseNil
新しいコンビネータでパーサーを組み合わせるのは簡単で、結果の種類はわかりやすいものです。
parseMulti :: Parser ([Int], ([Bool], ([String], Nil)))
parseMulti = mconcat <$> many1 (parseOne <* newline)
我々はタプルを再帰的にMonoid
インスタンスと複数の行を結合するためのリストのための通常のインスタンスに頼っています。 Right ([123,8],([True,False],(["acdf","qq"],Nil)))
として成功し解析し
runTest = parseTest parseMulti testInput
testInput = unlines [ "yes", "123", "acdf", "8", "no", "qq" ]
:今、それが機能することを示すために、簡単なテストを定義します。
+1;これが本当に必要な場合は素晴らしい実装です。 – ehird
私はこれがエレガンスに最も近いと思っています。幸いにも、私のタプルには5つの要素があります:-)。ありがとう! – aelguindy