對於這些類型的問題,我轉向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
解析器它的每個實例的類型類。在這裏,我們只定義實例爲Int
和Bool
:
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元產品轉換爲函數所期望的和的乘積需要Z
和SOP
構造函數。
好吧,讓我們定義的示例記錄並使其的Generics.SOP.Generic
一個實例:
data Foo = Foo { x :: Int, y :: Bool } deriving (Show, GHC.Generic)
instance Generic Foo -- Generic from generics-sop
讓我們來看看,如果我們可以分析Foo
與recParser
:
main :: IO()
main = do
print $ readP_to_S (recParser @Foo) "55False"
結果是
[(Foo {x = 55, y = False},"")]
泛型可以做到這一點。你可以在類似attoparsec的地方建立一個通用的解析器,並使用一個'Parseable'類型類,爲實現'Generic'的任何東西提供一個默認的實現。那麼你只需要'實例可解析用戶在哪裏'來解析它。 – bheklilr
很高興知道。我在哪裏可以找到更多細節?我沒有谷歌「attoparsec泛型可解析」,但是,搜索結果並不是很有幫助。 – user2812201
'Parsable'將會是你自己寫的類型類。 Attoparsec是一個在解析字節串中體面的庫。 Generic是一個內置的類型類,它提供了獲取可以在代碼中操作的數據類型的通用表示的函數。例如,aeson提供了一個'FromJSON'類型類,它可以利用'Generic',這樣你就可以做'FromJSON MyType where'而無需額外的工作來獲得將JSON解析爲'MyType'的值的能力。 – bheklilr