对于这些类型的问题,我转向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