对于这类问题我求助于仿制药-sop而不是直接使用泛型。仿制药-sop构建在泛型之上,提供以统一方式操作记录中所有字段的函数。
在这个答案中我使用ReadP附带的解析器base,但任何其他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
and 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)
The Code r ~ '[xs], All HasSimpleParser xs
约束意味着“该类型只有一个构造函数,字段类型列表为xs
,并且所有字段类型都有HasSimpleParser
实例”。
hcpure构造一个 n 元乘积 (NP)其中每个组件都是相应字段的解析器r
. (NP
products 将每个组件包装在类型构造函数中,在我们的例子中是解析器类型ReadP
).
然后我们使用hsequence将解析器的 n 元乘积转换为 n 元乘积的解析器。
最后,我们 fmap 到结果解析器中,并将 n 元乘积转回原始值r
记录使用to. The Z and SOP需要构造函数将 n 元乘积转换为乘积和to
函数期望。
好的,让我们定义一个示例记录并将其作为一个实例Generics.SOP.Generic
:
data Foo = Foo { x :: Int, y :: Bool } deriving (Show, GHC.Generic)
instance Generic Foo -- Generic from generics-sop
让我们检查一下是否可以解析Foo
with recParser
:
main :: IO ()
main = do
print $ readP_to_S (recParser @Foo) "55False"
结果是
[(Foo {x = 55, y = False},"")]