2017-03-03 71 views
4

我正在處理需要與服務器通信的網絡流式客戶端。服務器以字節串編碼響應,例如「1 \ NULJohn \ NULTeddy \ NUL501 \ NUL」,其中'\ NUL'是分隔符。上述響應轉換爲「這是類型1(由服務器硬編碼)的消息,告訴客戶端用戶的ID是什麼(這裏,」John Teddy「的用戶ID是」501「)。(通常)從自定義數據類型構建解析器?

那麼天真,我定義自定義數據類型

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

和對這種數據類型

parseID :: Parser User 
parseID = ... 

然後解析器一個只寫了一個處理程序做了一些工作(例如,寫入到數據庫)解析器成功完成這樣的響應之後,這非常簡單。

但是,服務器有幾乎100種類型的不同響應,如客戶端需要解析的那樣。我懷疑必須有一種更優雅的方式來完成這項工作,而不是像這樣編寫100個幾乎完全相同的解析器,因爲畢竟,所有haksell編碼器都是懶惰的。我是通用編程的全新手,所以有人可以告訴我是否有可以完成這項工作的軟件包?

+0

泛型可以做到這一點。你可以在類似attoparsec的地方建立一個通用的解析器,並使用一個'Parseable'類型類,爲實現'Generic'的任何東西提供一個默認的實現。那麼你只需要'實例可解析用戶在哪裏'來解析它。 – bheklilr

+0

很高興知道。我在哪裏可以找到更多細節?我沒有谷歌「attoparsec泛型可解析」,但是,搜索結果並不是很有幫助。 – user2812201

+1

'Parsable'將會是你自己寫的類型類。 Attoparsec是一個在解析字節串中體面的庫。 Generic是一個內置的類型類,它提供了獲取可以在代碼中操作的數據類型的通用表示的函數。例如,aeson提供了一個'FromJSON'類型類,它可以利用'Generic',這樣你就可以做'FromJSON MyType where'而無需額外的工作來獲得將JSON解析爲'MyType'的值的能力。 – bheklilr

回答

5

對於這些類型的問題,我轉向generics-sop而不是直接使用泛型。 泛型-sop構建於泛型之上,提供了以統一的方式處理記錄中所有字段的功能。

在這個答案我使用ReadP解析器,它隨基地,但任何其他Applicative解析器會做。一些初步進口:

{-# 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約束的意思是「這個類型有隻有一個構造函數,字段類型列表是xs,並且所有字段類型都有HasSimpleParser實例「。

hcpure構造一個n元產品(NP),其中每個組件是r對應字段的解析器。 (NP產品將每個組件包裝在一個類型構造器中,在我們的例子中是分析器類型ReadP)。

然後我們使用hsequence將解析器的n元積轉換爲n元產品的解析器。

最後,我們將fmap導入解析器,然後使用to將n元產品轉換回原始r記錄。將n元產品轉換爲函數所期望的和的乘積需要ZSOP構造函數。


好吧,讓我們定義的示例記錄並使其的Generics.SOP.Generic一個實例:

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

instance Generic Foo -- Generic from generics-sop 

讓我們來看看,如果我們可以分析FoorecParser

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) 

基本上木薯用戶可以分析所有的X相分離結構爲Vector,只要你能寫下FromRecord實例(這需要一個parseRecord :: Parser …功能工作。在Generic

附註直到最近我思考 - 一切 - 在haskell有一個通用的實例,或可以派生一個。好吧,這不是我想要序列化一些ThreadId CSV/JSON的情況,碰巧發現unboxed類型不是那麼容易「genericked」!

而且在我忘記它之前 - 當你談到流媒體和服務器等等時,有cassava-conduit可能是有幫助的。