5

レコードのパーサーを作成するために、haskellレコードのアプリケーションコンストラクターを一般的に作成したいと思います。

記録を考えてみましょう:

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

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

Record <$> pInt <*> pFloat

基本タイプのパーサーは次のとおりです。

class Parseable a where
  getParser :: Parser a

instance Parseable Int where
  getParser = pInt

instance Parseable Float where
  getParser = pFloat

すでにこれを実行できるライブラリはありますか?レコードのgetParserを定義することはおそらく可能ですか?前もって感謝します。

4

2 に答える 2

9

これは、たとえば通常のライブラリを使用して実行できます。このライブラリを使用するには、通常、いくつかの言語拡張機能が必要です。

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

import Control.Applicative
import Generics.Regular

最も人気のあるパーサーコンビネーターライブラリの少なくとも2つには、applicative-functorインターフェイスが付属しています。たとえば、uu-parsinglibparsecを参照してください。ただし、簡単にするために、ここでは単純な成功リストパーサーを使用しましょう。

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、単一の汎用インスタンスのみを保持します。どうぞ:FloatRecord

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

注:これは、通常のライブラリを使用して汎用パーサーを導出する方法の非常に基本的な例にすぎません。ライブラリ自体には、レコードで特に優れた処理を行う一般的な成功リストパーサーが付属しています。最初にそれをチェックすることをお勧めします。さらに、ライブラリにはTemplate Haskellのサポートが付属しているため、のインスタンスをRegular自動的に派生させることができます。これらのインスタンスには、レコードラベル用の特別な構造タイプが含まれているため、総称関数でレコードタイプを非常に凝ったものとして扱うことができます。ドキュメントをチェックしてください。

于 2012-07-10T15:54:38.220 に答える
3

regular私がパッケージを気に入っているのと同じくらいghc-7.2、GHCには一般的な表現型を導出するためのサポートが組み込まれているので、それを行うためにTemplateHaskellに依存する必要がないことを指摘したいと思います。

dblhelixによって提案されたソリューションと比較した変更点は次のとおりです。わずかに異なるフラグとモジュールをインポートする必要があります。

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

import Control.Applicative
import GHC.Generics

Parserとそのインスタンスは上記のように定義します。タイプのクラスを導出する必要がありGenericます。Record

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

クラスGenericはクラスと非常によく似ていRegularます。PF現在のインスタンスを定義する必要はありませんRegular

の代わりに、スタイルが非常に似ているが、わずかに異なるParseableFクラスを定義します。Parseable'

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、一般的なデフォルトのメソッドを定義します。

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

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

instance Parseable Record

この例は以前と同じように機能します。

> runParser (getParser :: Parser Record) "42 3.14"
[(Record {i = 42, f = 3.14},"")]
于 2012-07-11T15:11:34.263 に答える