2012-07-10 5 views
5

レコードのパーサを作成するために、一般的にhaskellレコード用のアプリケーションコンストラクタを作成したいと考えています。基本的なタイプのためレコードの適用コンストラクタ

Record <$> pInt <*> pFloat 

パーサが与えられている:

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} 

私が欲しいのコンストラクタ:

は、レコードを考えてみましょう?レコードのgetParserを定義することは可能でしょうか?前もって感謝します。

+0

だけ明確にする:あなたはあなたのために生成される '解析可能なRecord'のインスタンスをしたいですか? – kosmikus

答えて

9

これは、例えば、regularライブラリを使用して行うことができます。応用的-関手インターフェースが付属して最も人気のパーサコンビネータライブラリの

{-# LANGUAGE FlexibleContexts  #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE TypeFamilies   #-} 
{-# LANGUAGE TypeOperators  #-} 
{-# LANGUAGE UndecidableInstances #-} 

import Control.Applicative 
import Generics.Regular 

少なくとも2:このライブラリでの作業、一般的にいくつかの言語拡張を必要とuu-parsinglib、例えば、参照およびparsecが、簡単なものを維持するためにここで単純な成功リストパーサを使用しましょう。必要に応じて、あなたのレコードタイプのため、

class Parseable a where 
    getParser :: Parser a 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

そして:

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 

素直に、我々は今

data Record = Record {i :: Int, f :: Float} 

instance Parseable Record where 
    getParser = Record <$> pInt <* pSym ' ' <*> pFloat 

どのように一般的にそのようなパーサーを生成する?

まず、我々は(詳細についてはregularのマニュアルを参照してください)Recordのいわゆるパターンファンクタを定義します。

type instance PF Record = K Int :*: K Float 

その後、我々は型クラスRegularRecordインスタンスを作る:

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の元のインスタンス(つまり、IntFloat、もちろんRecordのインスタンス)を削除し、1つの汎用インスタンスのみを保持します。ここに私達は行く:

> runParser (getParser :: Parser Record) "42 3.14" 
[(Record {i = 42, f = 3.14},"")] 

注:これは、通常のライブラリを使用して、一般的なパーサを導出する方法のほんの非常に基本的な例です。図書館にはgeneric list-of-successes parserが付いています。これは特に素晴らしいことです。そのことを最初に確認したいかもしれません。さらに、ライブラリにはTemplate Haskellサポートが付属しており、Regularのインスタンスを自動的に派生させることができます。これらのインスタンスには、レコードラベル用の特別な構造体型が含まれているため、汎用関数で実際にレコード型を扱うことができます。ドキュメントをチェックしてください。

3

パッケージのように、ghc-7.2以降、GHCにはジェネリック表現型を派生させるための組み込みサポートが組み込まれているので、テンプレートHaskellに頼る必要はありません。

dblhelixで提案されている解決策と比較して、以下の変更点があります。あなたは、わずかに異なるフラグやモジュールがインポート必要があります。

{-# LANGUAGE DeriveGeneriC#-} 
{-# LANGUAGE DefaultSignatures #-} 
{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE TypeOperators #-} 

import Control.Applicative 
import GHC.Generics 

あなたはまだ、上記のようにParserとそのインスタンスを定義します。

data Record = Record { i :: Int, f :: Float } 
    deriving (Generic, Show) 

クラスGenericがクラスRegularと非常によく似ています: あなたRecordタイプのクラスGenericを導出する必要があります。 PFまたはRegularのインスタンスを定義する必要はありません。

代わりのParseableF、私たちはスタイルで非常に似ています、まだほんの少し異なるクラスParseable'を定義します。

class Parseable a where 
    getParser :: Parser a 
    default getParser :: (Generic a, Parseable' (Rep a)) => Parser a 
    getParser = to <$> getParser' 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

class Parseable' f where 
    getParser' :: Parser (f a) 

-- covers base types such as Int and Float: 
instance Parseable a => Parseable' (K1 m a) where 
    getParser' = K1 <$> getParser 

-- covers types with a sequence of fields (record types): 
instance (Parseable' f, Parseable' g) => Parseable' (f :*: g) where 
    getParser' = (:*:) <$> getParser' <* pSym ' ' <*> getParser' 

-- ignores meta-information such as constructor names or field labels: 
instance Parseable' f => Parseable' (M1 m l f) where 
    getParser' = M1 <$> getParser' 

は最後に、Parseableのために、我々は、一般的なデフォルトのメソッドを定義します

型を解析可能にすることは、空のインスタンス宣言を提供するのと同じくらい簡単です。

instance Parseable Record 

例は、以前に動作します:

> runParser (getParser :: Parser Record) "42 3.14" 
[(Record {i = 42, f = 3.14},"")] 
関連する問題