これは、たとえば通常のライブラリを使用して実行できます。このライブラリを使用するには、通常、いくつかの言語拡張機能が必要です。
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
import Control.Applicative
import Generics.Regular
最も人気のあるパーサーコンビネーターライブラリの少なくとも2つには、applicative-functorインターフェイスが付属しています。たとえば、uu-parsinglibとparsecを参照してください。ただし、簡単にするために、ここでは単純な成功リストパーサーを使用しましょう。
newtype Parser a = Parser {runParser :: ReadS a}
instance Functor Parser where
fmap f p = Parser $ \s -> [(f x, s') | (x, s') <- runParser p s]
instance Applicative Parser where
pure x = Parser $ \s -> [(x, s)]
p <*> q = Parser $ \s ->
[(f x, s'') | (f, s') <- runParser p s, (x, s'') <- runParser q s']
instance Alternative Parser where
empty = Parser $ \_ -> []
p <|> q = Parser $ \s -> runParser p s ++ runParser q s
(注意してくださいtype ReadS a = String -> [(a, String)]
。)
pSym :: Char -> Parser Char
pSym c = Parser $ \s -> case s of
(c' : s') | c == c' -> [(c', s')]
_ -> []
pInt :: Parser Int
pInt = Parser reads
pFloat :: Parser Float
pFloat = Parser reads
簡単に言えば、次のようなものがあります。
class Parseable a where
getParser :: Parser a
instance Parseable Int where
getParser = pInt
instance Parseable Float where
getParser = pFloat
そして、必要に応じて、レコードタイプについて:
data Record = Record {i :: Int, f :: Float}
instance Parseable Record where
getParser = Record <$> pInt <* pSym ' ' <*> pFloat
では、このようなパーサーを一般的にどのように生成するのでしょうか。
まず、のいわゆるパターンファンクターを定義します(詳細については、 regularRecord
のドキュメントを参照してください)。
type instance PF Record = K Int :*: K Float
次に、Record
型クラスのインスタンスを作成しRegular
ます。
instance Regular Record where
from (Record n r) = K n :*: K r
to (K n :*: K r) = Record n r
次に、汎用パーサーを定義します。
class ParseableF f where
getParserF :: Parser a -> Parser (f a)
instance ParseableF (K Int) where
getParserF _ = K <$> pInt
instance ParseableF (K Float) where
getParserF _ = K <$> pFloat
instance (ParseableF f, ParseableF g) => ParseableF (f :*: g) where
getParserF p = (:*:) <$> getParserF p <* pSym ' ' <*> getParserF p
(すべての通常のタイプをカバーするには、さらにいくつかのインスタンスを提供する必要がありますが、これらはあなたの例に役立ちます。)
これで、クラス内のすべてのタイプ(パターンファンクターRegular
のインスタンスが与えられた場合)にパーサーが付属していることを示すことができます。ParseableF
instance (Regular a, ParseableF (PF a)) => Parseable a where
getParser = to <$> getParserF getParser
試してみましょう。の元のインスタンスParseable
(つまり、、、およびもちろんのインスタンス)を削除しInt
、単一の汎用インスタンスのみを保持します。どうぞ:Float
Record
> runParser (getParser :: Parser Record) "42 3.14"
[(Record {i = 42, f = 3.14},"")]
注:これは、通常のライブラリを使用して汎用パーサーを導出する方法の非常に基本的な例にすぎません。ライブラリ自体には、レコードで特に優れた処理を行う一般的な成功リストパーサーが付属しています。最初にそれをチェックすることをお勧めします。さらに、ライブラリにはTemplate Haskellのサポートが付属しているため、のインスタンスをRegular
自動的に派生させることができます。これらのインスタンスには、レコードラベル用の特別な構造タイプが含まれているため、総称関数でレコードタイプを非常に凝ったものとして扱うことができます。ドキュメントをチェックしてください。