2017-03-03 19 views
4

私はサーバーと通信する必要があるネットワークストリーミングクライアントに取り組んでいます。サーバーは、「1 \ NULJohn \ NULTeddy \ NUL501 \ NUL」のように、バイトストリングで応答をエンコードします。ここで、 '\ NUL'は区切り文字です。上記の応答は "これはタイプ1のメッセージです(サーバーによってハードコードされています)。これはクライアントのID(ここでは" John Teddy "のユーザーIDは" 501 ")をクライアントに伝えます。(一般的に)カスタムデータ型からパーサーを作成しますか?

だから単純に、私はカスタムデータ型

data User 
    { firstName :: String 
    , lastName :: String 
    , id :: Int 
    } 

、もう1つは、単にいくつかの仕事をするためのハンドラを書き込み、このデータ型次に

parseID :: Parser User 
parseID = ... 

ためのパーサを定義する(例えば、データベースへの書き込み)パーサーがこのような応答を正常にマッシュした後は、非常に簡単です。

しかし、サーバーには、クライアントが解析する必要があるような、ほぼ100種類の異なる応答があります。結局のところ、すべてのhaksellコーダーは怠け者なので、このような100のほとんど同じパーザーを書くのではなく、仕事をする上でもっとエレガントな方法が必要だと思う。私はジェネリックプログラミングの初心者ですので、この仕事をするパッケージがあるかどうか教えてください。

+0

ジェネリックスがこれを行うことができます。 attoparsecのようなものの上にジェネリックパーサを構築し、 'Generic'を実装するためのデフォルトの実装を提供する' Parseable'型のクラスを作ることができます。それから、 'インスタンスParseable User where'が解析できるようにするだけです。 – bheklilr

+0

お役立ち情報詳細はどこで確認できますか?私はGoogleの "attoparsec一般的なparsable"しかし、検索結果は非常に有用ではなかった。 – user2812201

+1

'Parsable'は自分で書くタイプクラスです。 Attoparsecは、テストリングを解析するのにまともなライブラリです。 Genericは組み込みのtypeclassで、コード内で操作できるデータ型の汎用表現を取得する関数を提供します。たとえば、aesonは 'Generic'を利用できる' FromJSON'型のクラスを提供していますので、JSONを 'MyType'の値にパースする能力を得るために余分な作業をせずに' instance FromJSON MyType where'を実行できます。 – bheklilr

答えて

5

私はgenerics-sopに直接ジェネリックを使用するのではなく、このような問題について説明します。 generics-sopは、ジェネリックスの上に構築され、レコード内のすべてのフィールドを統一的に操作する機能を提供します。

私は ベースが付属していますが、他の Applicativeパーサがどうなる ReadPパーサーを使用この答えで

。いくつかの予備的な輸入:

{-# language DeriveGeneriC#-} 
{-# language FlexibleContexts #-} 
{-# language FlexibleInstances #-} 
{-# language TypeFamilies #-} 
{-# language DataKinds #-} 
{-# language TypeApplications #-} -- for the Proxy 

import Text.ParserCombinators.ReadP (ReadP,readP_to_S) 
import Text.ParserCombinators.ReadPrec (readPrec_to_P) 
import Text.Read (readPrec) 
import Data.Proxy 
import qualified GHC.Generics as GHC 
import Generics.SOP 

は、我々はそのインスタンスごとにApplicativeパーサを生成することができます型クラスを定義します。ここでは、IntBoolのためだけのインスタンスを定義します。

class HasSimpleParser c where 
    getSimpleParser :: ReadP c 

instance HasSimpleParser Int where 
    getSimpleParser = readPrec_to_P readPrec 0 

instance HasSimpleParser Bool where 
    getSimpleParser = readPrec_to_P readPrec 0 

今、私たちはすべてのフィールドがHasSimpleParserインスタンスを持っているレコードの一般的なパーサを定義します。

recParser :: (Generic r, Code r ~ '[xs], All HasSimpleParser xs) => ReadP r 
recParser = to . SOP . Z <$> hsequence (hcpure (Proxy @HasSimpleParser) getSimpleParser) 

Code r ~ '[xs], All HasSimpleParser xs制約はこのタイプが持っている」という意味します1つのコンストラクタのみ、フィールドタイプのリストはxs、すべてのフィールドタイプはHasSimpleParserのインスタンスです。

hcpureは、対応するフィールドのパーサーであるrのパーサであるn進数の積()を構成します。 (NPの製品では、各コンストラクタを型コンストラクタにラップします(この場合、パーサータイプはReadP)。

次に、n-ary製品のパーサにn-aryパーサーを変換するためにhsequenceを使用します。

最後に、得られたパーサにfmapを実行して、元のrレコードに戻ります(toを使用)。 ZおよびSOPのコンストラクタは、n-ary積を関数sumが期待する積に変換するために必要です。


OK]をクリックして、のは、例のレコードを定義し、それをGenerics.SOP.Genericのインスタンスを作成してみましょう:

data Foo = Foo { x :: Int, y :: Bool } deriving (Show, GHC.Generic) 

instance Generic Foo -- Generic from generics-sop 

は、私たちがrecParserFooを解析することができるかどうかは確認してみましょう:

main :: IO() 
main = do 
    print $ readP_to_S (recParser @Foo) "55False" 

結果が

です
[(Foo {x = 55, y = False},"")] 
4

あなた自身のパーサーを書くことはできますが、既にあなたのために解析できるパッケージがあります:cassavaですが、SOは通常ライブラリの推奨事項を検索する場所ではありませんが、解決策を探している人にこの回答を含めるしかし、これを実装し、すぐに使えるソリューションを探す時間はありません。

{-# LANGUAGE DeriveGeneriC#-} 
{-# LANGUAGE OverloadedStrings #-} 

import Data.Csv 
import Data.Vector 
import Data.ByteString.Lazy as B 
import GHC.Generics 

data Person = P { personId :: Int 
       , firstName :: String 
       , lastName :: String 
       } deriving (Eq, Generic, Show) 

-- the following are provided by friendly neighborhood Generic 
instance FromRecord Person 
instance ToRecord Person 

main :: IO() 
main = do B.writeFile "test" "1\NULThomas\NULof Aquin" 
      Right thomas <- decodeWith (DecodeOptions 0) NoHeader <$> 
           B.readFile "test" 

      print (thomas :: Vector Person) 

は、基本的にはキャッサバはあなたがparseRecord :: Parser …機能が動作するために必要FromRecordインスタンスを(書き留めることができて、VectorにすべてのX相分離構造を解析することができます。Generic

サイドノートを最近までI CSV/JSONにThreadIdをシリアル化して、ボックス化されていないタイプがあまり簡単に "生成されていない"ことを知りました!

私がそれを忘れる前に、あなたがストリーミングやサーバーなどを話すときには、助けになるかもしれないcassava-conduitがあります。

関連する問題